Cody Woolaver

Blog

Site updates, project notes, and the longer pieces that need more room than the home page.

Giving the pet archive a proper home

The pet archive had been one of those half-finished ideas I kept meaning to come back to. It started as a small personal page, then sat quietly while the rest of the site changed around it. When I returned to it, I wanted it to feel less like a folder of uploaded images and more like a real little corner of the site: organized by pet, easy to browse, and built with enough care that it could keep growing over time.

The data model now treats pets and photos as separate things. A pet can have life dates, a stable slug, a profile photo, and a display priority. A photo can be connected to more than one pet, carry a short description, and keep the date it was taken when that information is available. That shape matters because a lot of the best pictures are shared moments, and I did not want to duplicate the same file just because two names belonged beside it.

The upload path borrows from the ShareX storage work already in the site. Admin uploads go through the shared-file pipeline, validate that the file is an image, strip embedded EXIF data, and try to infer the photo date from the filename or image metadata before falling back to the upload time. The public pages then redirect image requests through the stored file variants, so the pet pages can use thumbnails, larger gallery images, and lightweight placeholders without each feature needing its own separate storage system.

On the visitor side, the goal is simple: make it pleasant to wander. The main page can be filtered by name or status, sorted by the order I choose, alphabetically, or by photo count, and each pet page can be searched, filtered by year, and opened into a lightbox. It is intentionally modest, but it gives the archive enough structure to feel like a project rather than a loose pile of files.

I also liked the excuse to go back through older pictures. Building the feature meant seeing faces from different parts of my life again: pets that are still here, pets I miss, and small ordinary photos that are worth keeping because of who was in them. That is the best kind of personal project for me, where the implementation is interesting but the reason for building it stays close to home.

View the pet archive

Read post

Building my own contact form spam check

While updating the contact form, I wanted a small barrier that fit the site instead of dropping in a large third-party widget. I could have used reCAPTCHA and moved on quickly, and for some products that would be the right call. For this site, part of the fun was treating the form as a small design problem: how far could I get with a focused, understandable solution that I owned end to end?

The weak spot was obvious. If the text is printed directly into the HTML, a basic script can collect it almost as well as a person can. So the server now creates a simple arithmetic check, breaks the characters into shuffled fragments, and stores those fragments in the markup in a lightly encoded form. The browser puts the text back together for visitors, while the raw page source is less useful to a drive-by bot.

The important part still happens on the server. The hidden token includes the issue time, a random nonce, and a keyed digest of the expected answer. When the form is submitted, the server checks the signature, rejects stale tokens, and compares the submitted value with the signed digest. That means the page can display the check without trusting the browser to decide whether it was solved.

This is not meant to be a universal anti-spam system. A determined attacker could inspect the JavaScript and automate around it. But for a personal contact form, the goal is more practical: avoid the simplest automated submissions, keep the experience quiet for real visitors, and learn from building the moving parts myself instead of immediately handing the job to another service.

Read post

Adding practical local services

I added a new page for local services outside of software: hauling, dump trailer work, flatbed trailer work, small-acreage tractor help, cleanup, and light chainsaw services around the Nashwaak Valley and Greater Fredericton area.

As much as I have a passion for building good technology, there is something deeply satisfying about using my hands to finish practical work. Moving material, clearing a property, preparing a garden area, or helping someone get a small job unstuck has a directness that I value. The work is visible, useful, and grounded in the place I live.

The new services page gives those jobs their own home on the site, with photos of the equipment, a clear description of what I can help with, and a simple way to call when someone wants to talk through whether a job is a good fit.

View local services

Read post

Taking image hosting back home

The ShareX project started from a practical itch: I wanted to stop leaning on public image-hosting services that I do not own. Those services are convenient until links rot, policies change, files get harder to move, or I simply want to know exactly where my screenshots and small uploads live. Wiring ShareX into this site gives me short public links on infrastructure I control.

The project ended up being more than an upload endpoint. It accepts screenshots, files, and text uploads behind a bearer token, writes incoming files through temporary storage, detects common image, video, and text types, and stores enough metadata to keep the original filename, size, media kind, and generated public path around. Public links serve images, videos, and text inline, while unknown files download instead.

Caching was one of the details that mattered. Public files get cache headers so repeated views are cheap, while responses also set nosniff and noindex so the links behave more like unlisted personal uploads than a searchable gallery. The admin side is where the system becomes manageable: I can upload manually, copy the custom ShareX config, sort by creation time, last view, or download count, see thumbnails and metadata, and mark files for deletion without immediately losing the bytes on disk.

The less glamorous parts were the real challenge: generating short URLs without collisions, keeping storage stable across Docker restarts, handling large uploads without reading everything into memory, tracking last-viewed and download counts without turning it into a full analytics system, and making deleted links stop resolving while still leaving room for recovery. It is small infrastructure, but it is mine, and that was the point.

Read post

Bringing this place back up to date

I have been refreshing this site from the inside out. Some of the work is visible, like the cleaner home and about pages and the Minesweeper project. A lot of it is quieter: updated dependencies, a more reliable asset build, Dockerized deployment, migrations, safer defaults, and a clearer path for keeping the app maintained.

The site is still intentionally small. It is a Pyramid application I have kept around because I like having a personal web space that is not only a profile page. I want it to be a place where I can show the practical software I build, write short notes about the decisions behind it, and leave useful projects online long enough for them to become part of the story.

I used modern coding tools here and there while doing the cleanup, but the point of the update is not the tooling. The point is to make the site feel more like mine again: easier to run, easier to change, and honest about where things are still in progress.

Over time I plan to add more project writeups, more detail about my work history, and a few small interactive pieces that are fun to use instead of just read about. For now, this is the start of turning the site from a placeholder back into a living portfolio.

Read post