Skip to content
~/francis-ohara_
all work

~/work/mule-mart

Mule Mart

An online marketplace for the Colby College community

A full-stack web app where verified Colby College students can buy, sell, and donate items. I started it with three other students, and as lead engineer I wrote most of the backend, including its semantic search feature and image storage service built on AWS S3.

PythonPythonFlaskPostgreSQLPostgreSQLPyTorchPyTorchSentenceTransformersSentenceTransformersAWS S3DockerDocker

Role

Lead Engineer

Timeline

Oct 2025 → Present

Semantic search

ranks listings by meaning instead of shared keywords

S3

private image storage, with uploads and downloads through presigned URLs

Docker

containerized, with automatic deploys through a Procfile

mulemart.com

Mule Mart interface

01 · the problem

Seniors and other students at Colby often throw away perfectly good things because they can't find anyone who wants them. The main place to post those things is Colby Now, the daily announcements email, but posts there are easy to miss and hard to find again. There's also no way to tell whether an item that was posted is still available. My team therefore wanted to build a better place for the Colby community to buy, sell, and donate things.

02 · what I built

  • Led the four-person team as lead engineer: set the technical direction, decided what to build and in what order, and split the work across the team.
  • Wrote most of the backend, a Flask app that stores data in PostgreSQL through SQLAlchemy.
  • Built Mule Mart's semantic search feature, which finds listings by meaning instead of matching keywords. It uses SentenceTransformers (all-MiniLM-L6-v2) and PyTorch to store each listing's title and description as a vector embedding, and it ranks results by cosine similarity.
  • Wrote the storage service for images, which live in a private AWS S3 bucket. Images are uploaded and loaded through presigned URLs, so large files never pass through the app server.
  • Containerized the app with Docker.
  • Wrote the three GitHub Actions workflows, which run the tests, linting, and the Docker build on every change.
  • Set up automatic deploys through a Procfile, which also runs database migrations on each release.

03 · how it works

  1. 01sign in

    Students sign in with Google or a password, using their Colby email.

    student@colby.edusigned in
    student@gmail.comPlease use your @colby.edu email address to sign in.
  2. 02post a listing

    The seller's photos upload from the browser straight to S3 through presigned URLs, and the listing is saved to PostgreSQL.

    browserprivate S3 bucket

    photos, through a presigned URL

    browserFlaskPostgreSQL

    listing details

  3. 03prepare for search

    When a listing is saved, its title and description are turned into a vector embedding with all-MiniLM-L6-v2 and stored with it.

    The North Face parka White down parka, size M. Kept me warm through two Maine winters.

    all-MiniLM-L6-v2

    [-0.001, 0.146, 0.078, 0.064, …, 0.011]

    384 numbers, stored with the listing

  4. 04search

    When a student searches, their search term is turned into an embedding with the same model and compared with every listing's embedding. Listings are ranked by cosine similarity, and those scoring below 0.25 aren't shown.

    winter jacketsimilarity
    The North Face parka0.54
    Winter boots0.46
    Wool beanie0.43
    Patagonia fleece pullover0.43
    Knit gloves0.29

    below 0.25, not shown

    Futon0.21
    Mini fridge0.20
  5. 05message and order

    Buyers message the seller in the app, and the chat checks for new messages every 2 seconds. Orders go to the seller to approve or reject. Once the seller approves an order, the listing is hidden from other buyers.

    Hi! Is the parka still available?

    Yes, it is. I can meet on campus tomorrow.

    order: pendingapproved

04 · key decisions

Keeping search in PostgreSQL

Instead of adding a separate vector database, I stored each listing's embedding in PostgreSQL next to the listing. At Colby's size, the app can compare a search against every listing directly, so one database was enough.

Uploading images straight to S3

Photos upload from the browser straight to S3 through presigned URLs, so large files don't pass through the app server and slow down other requests.

Checks on every change

I set up GitHub Actions to run the tests, linting, and a Docker build on pull requests and pushes to main, so problems show up before code is merged.

05 · where it stands

  • ✓Live at mulemart.com for the Colby College community.
  • ✓Students can search listings by meaning. For example, a search for “winter jacket” can find a listing titled “North Face parka”.
  • ✓I'm rebuilding the backend as event-driven Go microservices for Mule Mart 2.0.

06 · technologies used

PythonLanguage
FlaskBackend
PostgreSQLDatabase
PyTorchAI
SentenceTransformersAI
AWS S3Storage
DockerInfrastructure