mirror of
https://github.com/Hackdex-App/hackdex-website.git
synced 2026-10-07 08:19:05 -05:00
* Initial draft * docs: finalize Terms rollout dates and preserve previous version * feat: add reusable creator announcements and Resend sending script
184 lines
9.4 KiB
Markdown
184 lines
9.4 KiB
Markdown
# Hackdex
|
||
|
||
[](https://nextjs.org/)
|
||
[](https://react.dev/)
|
||
[](https://tailwindcss.com/)
|
||
[](https://supabase.com/)
|
||
[](https://aws.amazon.com/s3/)
|
||
[](LICENSE.md)
|
||
|
||
> **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](https://github.com/marcrobledo/RomPatcher.js); xdelta (VCDIFF) via a WASM build of [xdelta3](https://github.com/jmacd/xdelta) with glue from the Hackdex fork of [xdelta-wasm](https://github.com/Hackdex-App/xdelta-wasm) (forked from [kotcrab/xdelta-wasm](https://github.com/kotcrab/xdelta-wasm); statically linked [XZ Utils](https://tukaani.org/xz/) 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
|
||
|
||
## Security & legal notes
|
||
|
||
- **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=
|
||
NEXT_PUBLIC_DOWNLOADS_MILESTONE= # Empty hides the homepage celebration pill
|
||
|
||
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`.
|
||
|
||
For creator service notices, see [Creator announcements](docs/announcements/README.md) for the reusable email template and preview, recipient preparation, test, and send commands. The announcement script requires Node 22.18 or newer.
|
||
|
||
### Supabase (local)
|
||
|
||
Follow the official guide to run Supabase locally with the CLI (includes Studio):
|
||
|
||
- Supabase CLI getting started: [Local development with the Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=macos&queryGroups=access-method&access-method=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 repository’s 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`)
|
||
|
||
### S3‑compatible 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`)
|
||
|
||
### Seed data
|
||
|
||
After Supabase and MinIO are running, reset the database and upload dev fixtures:
|
||
|
||
```
|
||
npm run db:reset
|
||
```
|
||
|
||
This runs `supabase db reset` (migrations only — `[db.seed]` is disabled for CI), applies [`supabase/seed.sql`](supabase/seed.sql) via the local Supabase Postgres container, then uploads the shared BPS and xdelta patches plus cover images to MinIO via [`scripts/seed-storage.mjs`](scripts/seed-storage.mjs).
|
||
|
||
You can also run the steps separately:
|
||
|
||
```
|
||
supabase db reset
|
||
docker exec -i supabase_db_pokemon-romhack-platform psql -U postgres -d postgres -v ON_ERROR_STOP=1 < supabase/seed.sql
|
||
npm run seed:storage
|
||
```
|
||
|
||
**Dev login accounts** (password `Password1` for all):
|
||
|
||
| Email | Username | Role |
|
||
|-------|----------|------|
|
||
| `admin@hackdex.local` | `admin` | Admin (`claims_admin`) |
|
||
| `creator@hackdex.local` | `creator` | Regular creator |
|
||
| `creator2@hackdex.local` | `creator2` | Owns pending hacks |
|
||
|
||
**Seeded hacks (highlights):**
|
||
|
||
| Slug | Status | Purpose |
|
||
|------|--------|---------|
|
||
| `seed-emerald-demo` | Approved | Baseline discover, download, patch (`poke_emerald`); box art, social links; edit tags to see "New" badges |
|
||
| `seed-pending-demo` | Pending | Missing patch/screenshot warnings; unassigned |
|
||
| `seed-pending-ready` | Pending | Ready-for-review (patch + covers); assigned to admin; verification contact on the unverified owner |
|
||
| `seed-patcher-only-{account}-{1\|2\|3}` | Approved | Nine editable sandboxes (3 per account), patcher-only (`None` permission) |
|
||
| `seed-all-downloads` | Approved | Direct download on all published versions (`All`); custom patcher over 1.0 + 2.0 |
|
||
| `seed-current-only` | Approved | Direct download on current version only (`Current`); `Demo` completion status |
|
||
| `seed-draft-version` | Approved | Unpublished draft + publish UI |
|
||
| `seed-archived-version` | Approved | Archived older version + restore |
|
||
| `seed-third-party` | Approved | Third-party author display |
|
||
| `seed-xdelta-demo` | Approved | Published xdelta patch (`format = xdelta`) |
|
||
|
||
BPS patch rows share `seed-shared.bps`. The xdelta row on `seed-xdelta-demo` uses `seed-shared.xdelta`. Complete hacks have ≥3 cover images (`{slug}/cover-1.png` …) fetched from placehold.co during `seed:storage`.
|
||
|
||
To test in-browser patching on `seed-emerald-demo`, link a local **Pokémon Emerald** ROM (CRC32 `1f1c08fb`). The BPS fixture is [`public/patches/example_patch.bps`](public/patches/example_patch.bps), uploaded as `seed-shared.bps`. The xdelta fixture is [`public/patches/example_patch.xdelta`](public/patches/example_patch.xdelta), uploaded as `seed-shared.xdelta`.
|
||
|
||
**Note:** `seed:storage` requires `S3_*`, `PATCHES_BUCKET`, and `COVERS_BUCKET` in `.env.local` or `.env.development.local`, plus `NEXT_PUBLIC_HACK_COVERS_DOMAIN` for viewing covers in the app. It needs network access to placehold.co for cover images. If `PATCH_TOKEN_SECRET` and `PATCHES_DOWNLOAD_BASE_URL` are set, patch downloads route through the Cloudflare Worker instead of MinIO — unset those for local MinIO testing.
|
||
|
||
### 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](LICENSE.md)
|
||
- Branding Notice: the name “Hackdex” is reserved for use by the original project; see `LICENSE.md`
|