feat(admin): browse Discord suggestions in the portal
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
|
||||
The portal provides a read-only view of one Discord **forum channel**, using the existing NextAuth admin session and configured Keycloak role. Player sessions and Discord bot credentials are not accepted as API credentials. Sign in at `/admin/login` first; same-origin browser calls send the session cookie. Unattended machine authentication is not provided.
|
||||
|
||||
## Portal interface
|
||||
|
||||
Open `/admin/suggestions` from the administrator navigation. The idea desk lists active or archived forum posts with tags, status, approximate message counts, author IDs, timestamps, and Discord links. Select a title to open `/admin/suggestions/:id`, read the starter post and its reaction counts, and page through discussion newest-first. The starter is not duplicated in the discussion view.
|
||||
|
||||
The UI uses these same session-protected GET endpoints, not a second integration. Both pages independently check admin access before rendering; the APIs recheck it on every read. Expired/unauthorized API access offers an admin sign-in link. Loading, empty/deleted content, missing-text, and retryable failure states are explicit. Changing status aborts obsolete requests, and pagination restores keyboard focus to the page indicator. Text is rendered literally with React escaping, never as HTML or interpreted Discord Markdown. No bot token, forum configuration value, reply input, vote button, or moderation control is added to the client bundle.
|
||||
|
||||
Pagination history is page-local and resets when switching status or leaving the page. Reload the browser to refresh a view; upstream reads may use the documented 30-second cache. The real forum ID is still configured only through GitOps, not the UI or source.
|
||||
|
||||
## Runtime configuration
|
||||
|
||||
- `DISCORD_SUGGESTIONS_FORUM_ID`: required forum channel snowflake, configured through GitOps. No source-code default.
|
||||
@@ -21,7 +29,7 @@ The bot must have **View Channel** and **Read Message History** for the forum an
|
||||
| `/api/suggestions/:id` | Suggestion metadata and `originalPost`; `null` when the starter message was deleted. |
|
||||
| `/api/suggestions/:id/messages?limit=25` | Discussion messages, newest first, including the starter if reached. |
|
||||
|
||||
Lists return `{ items, nextCursor }`. Pass `nextCursor` back as the URL-encoded `cursor` parameter with the same status. Limits are integers from 1 to 100. Active cursors are thread IDs; archived cursors are UTC archive timestamps; message cursors are message IDs. Treat cursors as opaque. Messages may yield a final empty page because Discord does not provide a `has_more` flag for messages. Active threads are fetched via the guild active-threads endpoint, filtered to the forum, sorted, and paginated locally. Archives are paginated by Discord. This is a live view, not a consistent snapshot: threads can move between active and archived lists.
|
||||
Lists return `{ items, nextCursor }`. Pass `nextCursor` back as the URL-encoded `cursor` parameter with the same status. Limits are integers from 1 to 100. Active cursors are thread IDs; archived cursors are UTC archive timestamps normalized to `Z` while preserving fractional precision; message cursors are message IDs. Treat cursors as opaque. Messages may yield a final empty page because Discord does not provide a `has_more` flag for messages. Active threads are fetched via the guild active-threads endpoint, filtered to the forum, sorted, and paginated locally. Archives are paginated by Discord. This is a live view, not a consistent snapshot: threads can move between active and archived lists.
|
||||
|
||||
Suggestion fields: `id`, `title`, `authorId`, `createdAt`, `archived`, `locked`, `tags`, `messageCount`, `discordUrl`. Discord's message count is approximate, not a vote count. Detail messages include `id`, `author` (`id`, `name`), `content`, `createdAt`, `editedAt`, `reactions` (`emoji`, `count`), and `discordUrl`. Reactions remain reaction counts, not interpreted votes. Attachments, embeds, and rendered Discord Markdown are not mirrored; use Discord links for the original presentation.
|
||||
|
||||
@@ -42,6 +50,6 @@ The client checks the configured forum's guild/type and each requested thread's
|
||||
|
||||
## Verification before rollout
|
||||
|
||||
Run the source checks and focused tests in `apps/web/src/lib/discord/suggestions.test.ts` and `apps/web/src/app/api/suggestions/route.test.ts`. Under the separately approved GitOps/release plan, verify one known active post, one archived post, message content, and admin/non-admin access against the actual forum. Offline fixtures do not establish live Discord permissions or intent approval.
|
||||
Run the source checks and focused API tests in `apps/web/src/lib/discord/suggestions.test.ts` and `apps/web/src/app/api/suggestions/route.test.ts`, plus UI tests in `apps/web/src/components/suggestions-browser.test.tsx`, `suggestion-reader.test.tsx`, and `apps/web/src/app/admin/(console)/suggestions/`. Under the separately approved GitOps/release plan, verify one known active post, one archived post, message content, and admin/non-admin access against the actual forum. Offline fixtures do not establish live Discord permissions or intent approval.
|
||||
|
||||
References: [Discord threads](https://discord.com/developers/docs/topics/threads), [Channel resource](https://discord.com/developers/docs/resources/channel), [Message content intent](https://discord.com/developers/docs/events/gateway#message-content-intent).
|
||||
|
||||
Reference in New Issue
Block a user