Skip to main content

SwapGifts: a derangement solver, an Amazon affiliate scraper, and a paywall walk into a Bolt.new template

Share:XLinkedInHN
Cover for SwapGifts: a derangement solver, an Amazon affiliate scraper, and a paywall walk into a Bolt.new template

The problem I actually had

Every December someone in my extended family opens a group chat, proposes a Secret Santa, and loses two evenings to the same questions. Who draws whom. Can spouses draw each other. Where do we put the wishlists. The tools that exist want you to sign up, then want everyone in your family to sign up, then want an email address for each cousin who is not going to check email between now and Christmas.

I wanted the opposite. Create a group, get a link, share the link, done. If there are no accounts, the group link has to be the identity. If there are no emails, the wishlist has to live somewhere the browser remembers.

The repo is kaushiksaravanan/swapgifts, pushed in early March 2026. Public, TypeScript, nine commits. The initial one is around 14,900 lines, which tells you it was scaffolded from a template and I did not squash before pushing.

The derangement solver

The math problem inside a Secret Santa draw is a derangement: a permutation of a set where no element maps to itself. If Alice draws Alice, the draw is broken.

Real families need more than that. A spouse should not draw their spouse. A parent should not draw their own kid. Sometimes two siblings who fight every Christmas should not draw each other in either direction. A useful solver accepts an exclusion graph and produces a permutation that is a derangement and also respects every excluded edge.

src/utils/derangementSolver.ts is where that lives. The UI side is an ExclusionMatrix component: a grid of checkboxes where the organizer marks which pairs cannot be assigned. The solver treats those exclusions plus the identity diagonal as forbidden cells in an n by n matrix and searches for an assignment that avoids all of them.

The naive approach is shuffle and reject, which works fine for six people and falls off a cliff at fifteen when the exclusion matrix is dense. The solver in the repo is closer to a backtracking search: pick an assignee for participant one from the set of allowed targets, recurse, backtrack if the remaining subproblem has no solution. For the group sizes people actually run (family of ten, office of thirty) it returns in milliseconds.

Once you have this solver, running a Secret Santa becomes the easy part of the product. Everything else is landing pages.

What Bolt.new gave me and what I built on top

The repo has a .bolt/prompt and a .bolt/config.json in it. package.json still carries the default name vite-react-typescript-starter and the template is bolt-vite-react-ts. I did not rename it, which is honest about how the project started.

Bolt.new gave me a Vite plus React 18 plus TypeScript 5.5 plus Tailwind 3.4 baseline, an index.html, a few starter pages, and enough Tailwind config that I could stop thinking about the design system for the first evening. What I built on top:

  • Around 29 page components. CreateGroup, GiftFinder, GiftSelectionPortal, NameDrawingResult, WishlistPage, ChatPage, event landings for christmas, wedding, birthday, and office-party, a BudgetCalculator, a GiftIdeasGenerator, an admin dashboard, a blog, an FAQ, a refund policy, and a RevenueCat /autoaccept page.
  • Nine Supabase migrations covering groups, participants, exclusions, wishlists, chat messages, and the affiliate tables.
  • A hand-rolled router. App.tsx is a switch statement on window.location.pathname. No React Router, no Next.js. Every new page adds one more case.
  • A sitemap generator using xmlbuilder2, because 29 landing pages will not rank on Google without one.
  • Markdown rendering via marked for the blog and the wishlist notes.

Bolt.new is a good scaffolder for the first twenty minutes and after that you are on your own. The value is in how quickly it removes the "which router, which CSS, which build tool" decisions from the path.

Three monetization rails, honestly

There are three ways this app was set up to make money. I will tell you what each one is and where I know it worked versus where I do not.

AdSense. index.html and src/components/AdSense.tsx both carry the publisher id ca-pub-9276331695634022. Ad slots sit on the SEO landing pages, not inside the Secret Santa flow. The premise is standard: rank on "office secret santa ideas under 20 dollars", collect the search traffic, show ads next to the article. Whether the site ever earned meaningful money from this rail, I do not know from the repo alone.

Amazon affiliate. The tag swapgifts-21 appears in the gift ideas pages. When a user clicks through from a GiftIdeasGenerator result to a product on amazon.in, the referral credits back. The automation pipeline that fed this rail is next.

RevenueCat paywall. @revenuecat/purchases-js is in package.json. There is a paywall around a premium tier that I meant to unlock larger group sizes, custom themes, and priority support. The /autoaccept page is where RevenueCat sends users after a successful purchase. Whether any purchase ever cleared through it, again, I cannot tell you from the repo.

Three rails wired up, one algorithm that works, and no revenue dashboard in the source tree to prove any of them shipped meaningful dollars.

The Amazon automation pipeline

This is the part where I have to be careful about what I say.

Two Python files sit at the root of the repo. amazon_bestsellers_scraper.py is 673 lines of Selenium that opens Amazon India's Best Sellers pages, walks the categories, and pulls product titles, images, prices, and links. affiliate_automation.py is around 985 lines that takes the scraped rows, calls OpenRouter for a caption and Gemini for an image, and inserts the result into a Supabase table.

Then there is flow.n8n.txt, 646 lines of exported n8n workflow. The nodes read a Google Sheet of product links, enrich each row through the same OpenRouter and Gemini pair, and post the result to a Facebook page.

flowchart LR
    A[Amazon Best Sellers] -->|Selenium scrape| B[scraped_products.json]
    B --> C[affiliate_automation.py]
    C -->|OpenRouter caption| D[Supabase products table]
    C -->|Gemini image| D
    D --> E[Google Sheet mirror]
    E --> F[n8n workflow]
    F -->|caption + affiliate link| G[Facebook page]
    G -.->|clicks| H[amazon.in with tag=swapgifts-21]
    H -.->|commission| I[Affiliate dashboard]

I want to be honest about what this is. It is not a public good. It is a machine for taking bestseller listings, wrapping them in AI-generated marketing copy, and pumping them onto social feeds with an affiliate tag on the end. Whether that ever posted a single item to a live Facebook page, or the whole graph sat idle in the repo waiting for me to flip a switch, I cannot tell you from the source. There is no run log checked in. A 220 KB scraped_products.json in the repo root proves the scraper ran; nothing proves the poster did.

The reason I built it at all is that once I had 27 landing pages and an affiliate id, the marginal cost of "one more rail that feeds those pages with product cards" felt small. In retrospect that is the kind of reasoning that leads people to build spam factories in a weekend. I am glad it did not go live at scale.

What I would clean up before showing anyone

The repo hygiene here is loose. Six files match read_debug*.py and upload_debug*.txt. The 220 KB scraped_products.json sits in the root. Raw SQL fix files, SUPABASE_DELETE_FIX.sql and RECREATE_TABLE.sql, sit next to package.json. No CI, no vercel.json, no netlify.toml, no GitHub Actions. No README. No live URL referenced anywhere in the code, which means if you clone this today you will build it, run npm run dev, and have no idea whether the version you are looking at ever ran on a real domain.

If I picked this back up, the first pass would be: delete every read_debug* and upload_debug* file, move the SQL fixes into supabase/migrations where they belong, move scraped_products.json and the Python scrapers into a separate scripts/ folder with their own venv and README, and write a real top-level README that says what this is, how to run it, and where it is deployed. Then decide whether the affiliate pipeline gets rebuilt as a proper ETL job or gets deleted.

There is a solver in this repo I am proud of. There is also a lot of scaffolding around it that shows what "build a monetized SaaS in a week" looks like when you inventory the code afterwards. Both are useful to leave in the record.

Cite as: Saravanan, K. (2026). SwapGifts: a derangement solver, an Amazon affiliate scraper, and a paywall walk into a Bolt.new template. Kaushik Saravanan. https://www.kaushik.cv/blog/swapgifts-secret-santa-monetization