Jared Schoeny e0747efb82
Some checks are pending
Deploy Supabase Migrations to Production / migrate (push) Waiting to run
Add Discord review threads and a Resend reply loop for submitted hacks (#76)
* Add a Discord review thread and Resend reply loop for submitted hacks.

Admins can email submitters from a forum thread and inbound replies land back in that same thread.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add an admin endpoint to register Discord guild commands.

Local still uses the npm script; production can hit /api/discord/register while logged in as an admin.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add an admin backup to create a missing Discord review thread.

If submit fails to open a thread, an admin can create one from the hack details menu.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Match inbound review replies when the plus-address token is lowercased.

Mail delivers To in lowercase, so tokens are now hex and lookups are case-insensitive.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use distinct review embeds for /reply and inbound creator mail.

/reply posts a green embed with subject, Discord avatar, and the creator username; thread replies drop the To field.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Gate /reply by Discord role and mark inbound From as verified.

DISCORD_REPLY_ROLE_IDS is required; creator mail still posts when the address does not match the account.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Retry inbound review mail when Discord does not post the embed.

A failed thread post no longer records the email as processed or returns 200 to Resend.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Notify the admin webhook when a review thread cannot be loaded.

Patch upload no longer stays silent if getHackReviewThread throws.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Keep the /reply ack honest after the review email is sent.

A later Discord or deferred-response failure no longer claims the email failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Make the admin review message stand out in the reply email.

The quote sits in a rose-tinted box so it is not just another paragraph.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Tag review threads as claimed or unclaimed and announce claims.

New and backup-created threads get the matching forum tag; dashboard claims swap Unclaimed to Claimed.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 22:07:33 -06:00
2026-08-03 10:56:45 -06:00
2026-08-03 10:56:45 -06:00

Hackdex

Next.js React Tailwind CSS Supabase S3 Compatible License: MIT

Disclaimer: A good chunk of this code was initially written with AI assistance using Cursor. I later pledged to not use AI in future contributions, but due to life circumstances causing my time to be incredibly limited now, I made the difficult decision to resume using AI assistance in order to prevent Hackdex from going dormant and becoming neglected.

What is Hackdex?

Hackdex is a community hub for discovering and sharing Pokémon romhack patches. Players link their own legally obtained base roms once, then easily patch locally in the browser and download the patched rom. Hackdex only stores patches, screenshots, and metadata—never any roms—so distribution stays practical without any of the legal pitfalls. Creators get a consistent place to publish, version, and present their projects.

Core features

  • Discover: curated hacks with screenshots, tags, versions, and summaries
  • Submit: metadata, screenshots, social links, and a .bps or .xdelta patch file
  • Patch in the browser: BPS via RomPatcher.js; xdelta (VCDIFF) via a WASM build of xdelta3 with glue from the Hackdex fork of xdelta-wasm (forked from kotcrab/xdelta-wasm; statically linked XZ Utils liblzma); linked base roms stay on the user's device
  • Safe delivery: public urls for cover images, short-lived signed URLs for patch downloads and other assets; no rom storage required

Tech stack

  • Next.js 15 (App Router), TypeScript, React 19, Tailwind CSS 4
  • Supabase (Postgres, Auth, Storage) for data, auth, and cover images
  • S3-compatible object storage (Minio locally or preferred provider) for patch files (patches bucket)
  • In-browser patching with RomPatcher.js and xdelta3 WASM; local persistence with IndexedDB and the File System Access API

High-level architecture

  • UI: Next.js App Router with a mix of server and client components
  • Data: Supabase tables for hacks, tags, patches; cover images stored in covers S3-compatible bucket
  • Patches: stored in an S3-compatible bucket patches; downloads use short-lived signed URLs via an API route
  • Auth: Supabase SSR helpers manage cookies; client SDK for browser calls

Contribution guidelines

  • PRs welcome. Keep changes small, typed, and accessible
  • Prefer clear naming, early returns, and avoid any
  • Match existing Tailwind and component patterns
  • Hackdex does not host roms and never will; users supply them locally during patching
  • Patching happens entirely in the browser against the user's local file

Local development setup

Set up local Supabase for database, auth, and storage, and an S3-compatible bucket for patch files.

Prerequisites

  • Node.js 20+
  • Docker and the Supabase CLI
  • An S3-compatible service (Minio locally, or a cloud provider like AWS S3 or Cloudflare R2)

Environment variables (.env.local)

Provide your Supabase project URL, publishable key, public site urls, and S3 connection details:

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=

NEXT_PUBLIC_SITE_URL=
NEXT_PUBLIC_SITE_DOMAIN=

NEXT_PUBLIC_HACK_COVERS_DOMAIN=

S3_ENDPOINT=
S3_PORT=
S3_ACCESS_KEY_ID=
S3_SECRET_ACCESS_KEY=
S3_USE_SSL=

DISCORD_APPLICATION_ID=
DISCORD_PUBLIC_KEY=
DISCORD_GUILD_ID=
DISCORD_BOT_TOKEN=
DISCORD_REVIEW_FORUM_CHANNEL_ID=
DISCORD_REPLY_ROLE_IDS=
DISCORD_FORUM_TAG_PENDING_ID=
DISCORD_FORUM_TAG_APPROVED_ID=
DISCORD_FORUM_TAG_CLAIMED_ID=
DISCORD_FORUM_TAG_UNCLAIMED_ID=

RESEND_API_KEY=
RESEND_WEBHOOK_SECRET=
RESEND_INBOUND_DOMAIN=
RESEND_FROM=

Register Discord guild commands locally with npm run discord:register; in production, an admin can visit /api/discord/register.

Supabase (local)

Follow the official guide to run Supabase locally with the CLI (includes Studio):

Typical flow:

  1. Initialize and start services using the CLI
  2. Note the printed API URL and publishable key; set them in .env.local as shown above
  3. Apply this repositorys migrations in supabase/migrations
  4. Create a Supabase Storage bucket named hack-covers and make it public. Set the NEXT_PUBLIC_HACK_COVERS_DOMAIN environment variable to the public URL of your covers bucket (e.g., https://your-project.supabase.co/storage/v1/object/public/hack-covers)

S3compatible storage for patches and covers

  • Create a bucket named patches and a public bucket named covers
  • Point the S3_* environment variables to your S3 endpoint (Minio locally or your vendor)
  • Set the COVERS_BUCKET environment variable to the name of your covers bucket (e.g., covers)
  • Set the PATCHES_BUCKET environment variable to the name of your patches bucket (e.g., patches)
  • Set the NEXT_PUBLIC_HACK_COVERS_DOMAIN environment variable to the public URL of your covers bucket (e.g., http://localhost:9000/covers)

Install & run

npm install
npm run dev

For easier debugging in VS Code, use the launch configurations in .vscode/launch.json (full stack, server-side, or client-side).


License & Branding

  • MIT licensed; see LICENSE.md
  • Branding Notice: the name “Hackdex” is reserved for use by the original project; see LICENSE.md
Description
A website for easy hack discovering, uploading, and patching.
Readme 23 MiB
Languages
TypeScript 88.6%
PLpgSQL 8.8%
HTML 1.3%
CSS 0.9%
JavaScript 0.4%