Projects

GrabPic
Personal
After weddings and events, I kept doing the same thing: searching the album for one person's face, sending them their photos, and then starting over for the next person. GrabPic replaces that with a single link. The host uploads everything to one album, and each guest takes a selfie to get only the photos they appear in.
Here's how it works behind that link:
- It runs as three services that deploy on their own: a Next.js frontend, a Spring Boot API, and a Python worker that uses DeepFace to turn every face into an embedding. New photos reach the worker through an Amazon SQS queue, so uploads never wait on face processing.
- A selfie search is a nearest-neighbor lookup in pgvector over an HNSW index, which brings back matches from albums of 500+ photos in under 200 milliseconds.
- Photos upload straight from the browser to S3 through presigned URLs. Redis rate limiting and Cloudflare Turnstile keep bots out, and protected photos only show up for people whose face is in them.

PaperPulse
Personal
Keeping up with research means checking arXiv, PubMed, and a few other databases every day and sorting through a lot of papers that don't matter to you. PaperPulse does that search overnight, ranks everything against your interests, and has the 25 most relevant papers waiting in your feed the next morning. You can also ask it questions and get answers pulled from the papers themselves, with citations.
Here's what happens each night and when you ask a question:
- The nightly pipeline pulls from arXiv, Semantic Scholar, PubMed, and OpenAlex, extracts the text from each PDF, and embeds it for search, then Cohere reranks the results for each user.
- Answers come from a three-stage hybrid retrieval pipeline over pgvector, plus context from a Neo4j knowledge graph that links papers to their authors, concepts, and citations.
- For literature reviews, an AI agent explores that graph on its own, following citations and shared concepts to find themes and gaps before it writes the review.
- Built with FastAPI and Next.js. The backend and its Neo4j graph run as Docker containers on my home server.

Home Server
Personal
Two of my personal projects, GrabPic and PaperPulse, used to run on AWS for about $100 a month. I moved them onto an Acer Nitro 5 gaming laptop at home, along with Queue Up and a few apps I use myself. Now everything runs for about $1 a month. The live page shows every container on the server and the laptop's CPU, memory, and GPU readings as they change.
Here's what runs on it and how traffic gets in:
- Visitors never connect to my home network directly. A cloudflared container keeps an outbound tunnel open to Cloudflare, which handles DNS and HTTPS, so my router has no open ports and my home IP address stays hidden.
- When I push to GitHub, Coolify builds the app into a container and sets up its route in Traefik, which passes each request to the right app. None of the databases publish a port, so only containers on the same Docker network can reach them.
- Apps for my own use, like Immich for photo backup and Paperless-ngx for scanned documents, are reachable only from my phone and laptop over Tailscale. Uptime Kuma checks every app and alerts me when one goes down.
- The live page is a small Next.js app that reads the host, Docker, and Uptime Kuma only while someone has it open, and streams the readings to the browser with Server-Sent Events.

Queue Up
Personal
Queue Up is for meeting people through music. It reads your Spotify history, from top artists to saved songs, shows you the people whose taste overlaps with yours the most, and lets you start chatting once you both swipe right.
The matching and chat are built like this:
- Matches are ranked with a weighted score, where a shared favorite artist counts for more than a shared saved song, and each profile shows exactly what you have in common.
- Chat runs over WebSockets with typing indicators, online status, and live match notifications, and file attachments go straight to S3 through presigned URLs.
- The React frontend is compiled into the Spring Boot app, so the whole thing ships as one Docker image. It runs on my home server alongside its PostgreSQL database, and logins use JWTs stored in HTTP-only cookies.

CoSign
Personal
It's easy to ignore a to-do list when nobody's checking. In CoSign, someone you pick has to approve your proof before a task counts as done, and if the deadline passes first, they get emailed a penalty you wrote ahead of time and would rather keep private.
I built it to be hard to cheat:
- Each task moves through a state machine from waiting on proof to approved or missed, and deadlines are enforced automatically.
- Penalties are encrypted with AES and stay hidden until a deadline is missed, and each one is hashed so it can't be reused after it's exposed.
- Built with Spring Boot, React, and TypeScript, with live WebSocket updates and recurring tasks, all shipped as one Docker container.
PandOS
Personal
An operating system kernel I wrote in C for uMPS3, an emulator of a MIPS computer. It runs up to 20 processes at once and switches between them every 5 milliseconds, so each one gets a fair turn on the CPU.
The kernel handles the rest of the low-level work too:
- Processes wait on semaphores for devices and for each other, and the kernel handles their system calls and device interrupts.
- Each process runs in its own virtual address space, mapped to physical memory through the TLB.
BitTorrent Client JS
Personal
A BitTorrent client I wrote in Node.js. Give it a .torrent file or a magnet link and it finds peers, connects to them directly, and downloads the file piece by piece.
I built each part of the protocol from scratch:
- It includes its own bencode encoder and decoder, finds peers through HTTP trackers, and speaks the BitTorrent wire protocol over TCP.
- Every piece is checked against its SHA-1 hash before the file is put back together.
- For magnet links, which don't include the file's metadata, it uses the extension protocol to get that metadata from peers first.