Add MineGeo gameplay systems and interface

This commit is contained in:
2026-10-10 10:27:47 +05:00
parent bf01d9801d
commit 9af71c85c2
47 changed files with 9954 additions and 93 deletions

View File

@@ -40,7 +40,7 @@ The follow-up migration `20261008075322_profile_identity.sql` adds a permanent
**Bug finder** badge when a glow is granted. Revoking the glow preserves the badge.
Guest reports retain confirmed-bug recognition when linked after a glow revoke.
Players with an active glow can choose any hex color in **Settings → Username glow**.
Moderators display a shield badge from their account role and can find an account
Moderators display a shield badge, and Admins display a crown. Admins can find an account
in **Moderation → Player prefixes** to assign, change, or remove a custom text/icon
prefix. Account IDs disambiguate players with identical names. Prefixes do not
change permissions. These decorations appear in account views, leaderboards,

135
docs/discord-integration.md Normal file
View File

@@ -0,0 +1,135 @@
# Discord announcements
MineGeo uses your Discord bot to send messages through Discord's REST API.
It runs on Vercel and does not need a continuously running bot process, Gateway,
Message Content intent, slash commands, an interactions URL, or a Discord webhook.
The bot can appear offline while announcements work.
New maps are announced when a Moderator or Admin approves their first public
version. Drafts, imported ZIP copies, player contributions, subsequent map updates,
and re-publication do not create new-map announcements. Existing maps from before
this integration are not backfilled.
In an open multiplayer lobby, the host can select **Publish party to Discord**.
All published parties go to the single server-wide `DISCORD_PARTIES_CHANNEL_ID`.
Players cannot choose a destination. Discord readers can open the invite and join
without a MineGeo account while the lobby is open and has space. Messages show
the map, host, difficulty, player count, and link. Publishing is optional: ordinary
private invite links still work. Parties on private or imported maps cannot be
announced publicly. A room gets one announcement, including repeated clicks or
a transfer of lobby ownership. The counts are a snapshot when posted.
## Create and invite your bot
1. Open the [Discord Developer Portal](https://discord.com/developers/applications)
and select **New Application**. Name it **MineGeo**, or your preferred bot name.
2. Open **Bot**. Create the bot if prompted, then use **Reset Token** to obtain
its bot token. Keep it private; save it directly into Vercel as described below.
3. Under **Installation**, enable **Guild Install**. For the guild's install
settings, choose the **bot** scope and these permissions: **View Channels**,
**Send Messages**, **Embed Links**. You do not need Administrator permission.
Use the installation link to add the bot to your Discord server. Alternatively,
**OAuth2 → URL Generator** with the `bot` scope generates an invite link.
4. Create or choose a text channel for map announcements and another for parties.
You can use the same channel for both. Ensure the bot has the three permissions
above in each channel, including any category or channel permission overrides.
Ensure community members can view the party channel.
5. In Discord's **User Settings → Advanced**, enable **Developer Mode**. Right-click
each channel and choose **Copy Channel ID**. On mobile, long-press the channel
or open its channel menu, then choose **Copy Channel ID**. Copy the numeric
channel IDs; an invite link or server ID is not a channel ID.
Discord setup references: [application setup](https://docs.discord.com/developers/quick-start/getting-started),
[bot permissions](https://docs.discord.com/developers/topics/permissions).
## Add the Vercel environment variables
Open [MineGeo's environment settings](https://vercel.com/pavel-melnikovs-projects-74f979c2/minegeo/settings/environment-variables).
Add these for **Production**:
| Name | Value | Sensitive? |
| --- | --- | --- |
| `DISCORD_BOT_TOKEN` | Bot token from the Developer Portal, without `Bot ` in front | Yes |
| `DISCORD_MAPS_CHANNEL_ID` | Numeric channel ID for new approved maps | No |
| `DISCORD_PARTIES_CHANNEL_ID` | Numeric channel ID where **all published parties** appear | No |
| `MINEGEO_URL` | `https://minegeo.vercel.app` (already configured) | No |
Use **Sensitive** for the token, and never prefix these names with `NEXT_PUBLIC_`.
Leave a channel variable blank to disable that announcement type. A Discord
invite such as `MINEGEO_DISCORD_URL` controls the footer button only; it cannot
send announcements.
After saving, open **Deployments**, choose the newest Production deployment,
select **Redeploy**, and use the current environment variables. Vercel applies
environment changes to new deployments. See [Vercel's environment-variable guide](https://vercel.com/docs/environment-variables).
For local use, put the same variables in ignored `.env.local` (ordinary `.env`
also works) and restart the server. Use a publicly reachable HTTPS `MINEGEO_URL`
for links that Discord members can open. Do not paste bot tokens into chat,
source control, Linear issues, or public map metadata.
## Check it
Sign in as **PerfectPython500**, open **Moderation → Discord integration**, and
select **Check Discord setup**. This reads the bot and channels without sending
a test message. It confirms the bot token, channel visibility, and channel type;
the bot must also have **Send Messages** and **Embed Links**.
Approve a genuinely new map to test a map announcement. Create a multiplayer room
on a published map, choose **Publish party to Discord**, and open the resulting
link from Discord. The lobby shows queued, sent, expired, or failed delivery.
## Saved delivery and retries
Announcements are stored in a private Supabase queue. Delivery runs after map
approval or party publication, leaving gameplay and approval independent of
Discord availability. Active workers hold leases, repeated clicks share one event,
and Discord receives a stable nonce with `enforce_nonce`. Rate limits and temporary
errors retry with a delay, up to eight attempts. Invalid tokens and missing channel
permissions require fixing the settings and selecting **Retry saved announcements**
as an Admin. Delivery resumes while the host polls their lobby or an Admin opens
the integration panel; other announcements also wake pending jobs.
Started, closed, full, removed, private, or stale parties are skipped. Pending
party announcements expire one hour after sharing; lobbies themselves last up to
24 hours. A map removed before delivery is skipped. Messages already posted are
kept when the match starts; the linked lobby shows its live state. When a party
closes (including its last player leaving), its posted announcement turns red,
says **Party closed**, and removes the join prompt. Five minutes after Discord
acknowledges that notice, the bot deletes the message. Vercel Workflow stores the
timer and resumes deletion even if every player closes the page. Retries preserve
the original deadline and tolerate messages already removed by a moderator.
Previously posted announcements for closed or deleted rooms receive the same
cleanup when the queue next runs. The original message's channel is used even
if `DISCORD_PARTIES_CHANNEL_ID` has since changed. Cleanup failures appear in the
Admin integration panel and can be retried after fixing bot access.
Discord only deduplicates nonces for a few minutes. A long outage after an
unconfirmed send can therefore result in a duplicate; inspect the channel before
manually retrying uncertain deliveries.
For optional scheduled retries, configure a private `CRON_SECRET` and call
`GET /api/integrations/discord/dispatch` with `Authorization: Bearer <CRON_SECRET>`.
This endpoint claims at most five due announcements. A Vercel Cron entry can use
that path; scheduling limits depend on your plan. No schedule is installed by
default, and the secret must never be placed in a URL.
## Database and verification
Apply `20261009133431_discord_announcements.sql` and
`20261009153708_discord_party_cleanup.sql` after existing migrations.
The queue and functions are service-role-only, use RLS and invoker permissions,
and verify the host from the room's server-verified access hash. No Discord
credentials are stored in Supabase or sent to a browser.
Vercel Workflow uses the project's managed backend automatically. Keep **Settings
→ Environment Variables → Enable access to System Environment Variables** enabled
and redeploy after changing it. No additional secret or scheduled cron is needed
for the five-minute timer. The bot edits and deletes its own announcements, so
**Manage Messages** is not required.
Run `npm test`, `npm run typecheck -- --incremental false`, `npm run build`, and
`node scripts/test-discord-sql.mjs`. `node scripts/test-discord-http.mjs` verifies
the live role gates, destination restrictions, optional configuration, and
host-only room action with temporary accounts and a room, which it removes.
Discord transport tests use a mocked API; real delivery requires your bot token
and channel permissions.

View File

@@ -1,11 +1,13 @@
# Moderator access
# Admin and Moderator access
MineGeo checks `public.profiles.role` on the server for every moderation request. A contributor cannot change their own role through the website or Supabase's public API. No Discord role, JWT metadata, or additional plugin is required.
1. Have the moderator sign in to MineGeo and open their dashboard once.
2. In the **MineGeo** Supabase project, open **Authentication → Users** and copy that person's user UUID.
3. Open **Table Editor → public.profiles**, find the matching `id`, and set `role` to `moderator`. Keep the existing ID and display name.
4. The moderator can open `/moderation`, inspect each location in 360°, approve a submitted map, return it with feedback, unpublish a map, and resolve community reports. The role takes effect on the next request.
3. Open **Table Editor → public.profiles**, find the matching `id`, and set `role` to `moderator` or `admin`. Keep the existing ID and display name.
4. Moderators can open `/moderation`, inspect pending maps and updates in 360°, approve them, or return them with feedback. Admins also manage the beta badge, player prefixes, community reports, ban appeals, and unpublishing. The role takes effect on the next request.
Apply `20261009123642_admin_permissions.sql` before deploying this role split. The account **PerfectPython500** has Admin permission. The username shows an Admin crown; Moderators retain their shield. Neither role can be assigned by editing a player profile through the website or a public Supabase client. Admin assignment remains a project-owner operation in Supabase.
Alternatively, the project owner can run this in Supabase's SQL Editor, replacing the email with the intended account:
@@ -26,9 +28,9 @@ Treat the website role separately from Supabase organization membership: moderat
Signed-in players can use **Report content** on a published map page to report the entire map, or during a game to report the current panorama. Reports include a category, a 10–2000 character explanation, and an optional HTTP/HTTPS evidence link. The website derives panorama identity from the player's accessible game round; it retains the reported immutable revision for moderator inspection. Owners cannot report their own maps. Each person can have only one open or confirmed report per map until its ban is appealed successfully; dismissed reports can be corrected and resubmitted.
In `/moderation`, inspect the reported surface map and 360° locations, then **Confirm report · add strike** or **Dismiss report**, with written feedback. Pending and dismissed reports never count toward bans. Each distinct reporter can contribute one confirmed strike. Confirming the third report atomically unpublishes the map and blocks publication. The ban applies to the map, not the owner's account. Existing games retain their original revisions; the map disappears from the public catalog and cannot start new games.
In `/moderation`, Admins inspect the reported surface map and 360° locations, then **Confirm report · add strike** or **Dismiss report**, with written feedback. Pending and dismissed reports never count toward bans. Each distinct reporter can contribute one confirmed strike. Confirming the third report atomically unpublishes the map and blocks publication. The ban applies to the map, not the owner's account. Existing games retain their original revisions; the map disappears from the public catalog and cannot start new games.
The owner opens the banned map from `/dashboard` and uses **Appeal map ban**. One appeal can await review at a time. Moderators can inspect the map and approve or reject the appeal with feedback. Rejection keeps the ban and permits another appeal with new information. Approval restores the last published revision, resets the three-strike counter, closes pending reports from the old cycle, and preserves all report and appeal history. A moderator cannot confirm their own report, confirm reports on their own map, or approve their own map's appeal.
The owner opens the banned map from `/dashboard` and uses **Appeal map ban**. One appeal can await review at a time. Admins can inspect the map and approve or reject the appeal with feedback. Rejection keeps the ban and permits another appeal with new information. Approval restores the last published revision, resets the three-strike counter, closes pending reports from the old cycle, and preserves all report and appeal history. An Admin cannot confirm their own report, confirm reports on their own map, or approve their own map's appeal.
Contribution uploads older than five days are hidden from the dashboard and the owner's **Player submissions** list. Nothing is deleted: direct contribution links, accepted panoramas, and quota accounting still work. The cutoff uses the contribution's creation time, including pending submissions.
@@ -37,8 +39,13 @@ Apply `supabase/migrations/20261006074343_content_reports_and_appeals.sql` after
```sh
npm install --prefix .data/sql-verification --no-save --package-lock=false @electric-sql/pglite@0.3.14
node scripts/test-moderation-sql.mjs
node scripts/test-admin-permissions-sql.mjs
```
After deployment, `node scripts/test-admin-permissions-production.mjs` verifies the
live API using disposable player, Moderator, and Admin accounts. It removes its
own maps and accounts afterward. It does not require a browser.
The reporting migration was applied to the configured **MineGeo** Supabase project on October 6, 2026 after `/api/v1/moderation` returned `PGRST205` for the missing `map_appeals` table. The live Data API now successfully reads moderation maps, open reports, and the appeal queue. Appeal RLS is enabled, and public roles cannot read appeals or execute moderation decisions. The earlier `map_refresh_fixes` migration remains unapplied; applying it was rejected by automatic approval review as outside this reporting fix.
# First beta map

View File

@@ -1,5 +1,23 @@
# Deploy MineGeo
Optional Discord map and multiplayer-party announcements use `DISCORD_BOT_TOKEN`,
`DISCORD_MAPS_CHANNEL_ID`, and `DISCORD_PARTIES_CHANNEL_ID`. All published parties
use the same configured channel. Follow [Discord bot setup](discord-integration.md)
for creation, permissions, Vercel variables, and retries. Apply the Discord queue
migration before deploying this feature; the bot token stays server-only.
Optional community buttons appear in the footer when `MINEGEO_DISCORD_URL` or
`MINEGEO_TELEGRAM_URL` is configured. Set these public links in `.env.local` for
local development (ordinary `.env` also works). Restart the local server after
changing a link. For the hosted site, add the same names as Production Config
variables in the MineGeo Vercel project, then redeploy. These values are public
destinations; no Discord bot token or Telegram API key is needed.
Discord accepts HTTPS links on `discord.gg`, `discord.com`, `www.discord.com`,
and `discordapp.com`. Telegram accepts `t.me` and `telegram.me`, including group
invite links. Blank, malformed, or unsupported URLs hide the corresponding
button. Each button opens its configured destination in a new tab.
Run `npm run deploy` from the project directory. The script collects all web
source files and public assets, uploads them in one request, waits for the build,
and prints the deployment URL. It uses the [Vercel deployments API](https://vercel.com/docs/rest-api/deployments/create-a-new-deployment). The database credentials already configured in