You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add developer blog under /blog with admin editor and public RSS
Adds a full blog subsystem to the Downloads site so we can publish
release notes and API write-ups under blog.bentobox.world (or
download.bentobox.world/blog). Phase 1 + 2 in one drop:
- BlogManager backed by data/Blog.sqlite with posts + post_revisions
- markdown-it server-side rendering with highlight.js syntax highlighting
- GitHub-style alert blockquotes ([!NOTE], [!TIP], …) become callouts
- Admin tab with split-pane Markdown editor, live preview, autosave,
image upload, tag input, datetime-local scheduling
- Public list + permalink + tag archive + RSS 2.0 + sitemap.xml
- /blog/p/:slug is server-rendered with OG / Twitter Card / JSON-LD
- Per-minute cron promotes scheduled posts and pings Discord webhook
- New canAuthorBlog flag on users (admins implied), pre-grantable via
env.blog_author_discord_ids; cookie_domain support so a single
Discord login covers download.* and blog.*
Copy file name to clipboardExpand all lines: CLAUDE.md
+14-1Lines changed: 14 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -46,13 +46,26 @@ yarn site # Frontend-only dev rebuild + start server
46
46
- Images live at `weblink/blueprints/images/<gameMode>/<name>.png` (recommended 800×450, with optional `<name>.thumb.png` at 400×225). The catalog auto-detects them at sync time when the overlay does not specify `image`. The Express handler at `src/index.ts` serves them under `/blueprints/images/...` with a path-traversal guard and PNG-only whitelist.
47
47
- Endpoints: `GET /api/blueprints` (full catalog), `GET /api/blueprints/download?id=<gameMode>/<name>&type=blueprint|bundle` (single file), `GET /api/blueprints/zip?ids=[...]|gameMode=<gm>` (multi-select or whole-game-mode zip). IDs are validated against a strict regex and resolved paths are checked against the blueprints root before any read.
- Backed by `data/Blog.sqlite` via Sequelize. Models: `posts` (slug-keyed permalinks, draft/published, markdown source + sanitized HTML cache, view counter) and `post_revisions` (one row per save, pruned daily after 90 days).
51
+
- Markdown source is rendered server-side with `markdown-it` + `sanitize-html` on every save; the HTML is what's served to readers, so the public path doesn't ship a markdown engine. The admin editor preview uses `react-markdown` + `remark-gfm` + `remark-breaks` client-side.
52
+
- Permissions: an author is anyone with `users.isAdmin = true` OR `users.canAuthorBlog = true`. Pre-grant via `env.blog_author_discord_ids` (mirrors `admin_discord_ids`). `requireBlogAuthor` middleware in `auth.ts` gates writes; the Admin page filters its tab list to only "Blog" for users who are blog authors but not full admins.
53
+
- Endpoints: `GET /api/blog/posts?page&tag` (paginated public list, optional tag filter), `GET /api/blog/posts/:slug` (public, bumps view count), `GET /api/blog/tags` (aggregated tag counts across published posts), `GET /api/blog/feed.xml` (RSS 2.0), `GET /blog/sitemap.xml` (sitemap), `GET /blog/highlight.css` (highlight.js theme served from the bundled github-dark theme with our background override). Author endpoints under `/api/blog/admin/posts` (CRUD), `/api/blog/admin/posts/:id/publish` (with optional `{ at: <epoch_ms> }` body to schedule), `/api/blog/admin/posts/:id/unpublish`, `/api/blog/admin/images` for multipart uploads (5 MB cap, PNG/JPG/WebP/GIF only). All writes require `X-Csrf-Token` and `requireBlogAuthor`.
54
+
- Permalinks at `/blog/p/:slug` are server-rendered with OG/Twitter Card meta + JSON-LD Article schema + canonical URL by `BlogManager.renderPostPage()`. Express checks for the path in `src/index.ts` before falling through to the SPA. On `blog.bentobox.world`, the bare `/p/:slug` and `/feed.xml` paths also work via a hostname check (`isBlogHost`).
55
+
- On `draft → published` transition the manager fires a Discord webhook configured by `env.discord_blog_webhook_url` (full URL or `/api/webhooks/...` path). Failures are logged and ignored — they never block the publish.
56
+
- Subdomain plumbing: in production set `env.cookie_domain = ".bentobox.world"` so Discord sessions cover both `download.*` and `blog.*`. Recommended nginx config: a single `server` block for `blog.bentobox.world` that rewrites `/(.*)` to `/blog/$1` before proxying to the same upstream `:8080` — keeps the React Router and SSR paths uniform.
57
+
- Uploaded images live under `data/blog/images/YYYY/MM/<sha256-prefix>.<ext>` and are served by Express via the `/blog/images/...` static handler (path-traversal guarded, MIME whitelist).
58
+
- Markdown extras: code fences are server-side syntax-highlighted via `highlight.js` (auto-detect for unfenced langs); GitHub-style alert blockquotes (`> [!NOTE]`, `[!TIP]`, `[!IMPORTANT]`, `[!WARNING]`, `[!CAUTION]`) are post-processed into `<div class="callout callout-X">` boxes with coloured labels.
59
+
- Scheduled posts have `status='scheduled'` and a future `publishedAt`. A per-minute cron promotes them to `published` and fires the Discord webhook at promotion time, not at scheduling time.
60
+
- Phase scope: Phase 1 = list + permalink + RSS + OG + Discord webhook + image uploads + draft/publish, single author. Phase 2 (current) adds tags + tag archive, code highlighting, GFM callouts, scheduled publishing, sitemap. Phase 3 will add multi-author grant UI, comments (Giscus), search, dev.to cross-post, git backup, analytics.
-`POST /api/blueprints/submit` accepts a multipart upload (max 5 MB), JSON-parses, ajv-validates against `src/api/schemas/blueprint.schema.json` (vendored from `bentobox/`), runs `validateBlueprintSubmission` (rejects command-block materials and opaque `inventory` payloads), checks slug collision against the local clone, then opens a PR on `BentoBoxWorld/weblink` via Octokit using `env.weblink_github_token` — branch `submit/<discordId>/<slug>-<ts>` with two file commits (the `.blueprint` and the patched `catalog.json`).
54
67
- Helmet config in `src/index.ts` is the canonical CSP — Discord OAuth navigation works without an explicit allowlist because helmet's CSP does not constrain top-level navigation.
55
-
-`env.json` keys: `discord_client_id`, `discord_client_secret`, `discord_redirect_uri`, `weblink_github_token` (fine-grained PAT scoped to `BentoBoxWorld/weblink` with `contents: write` + `pull-requests: write`).
68
+
-`env.json` keys: `discord_client_id`, `discord_client_secret`, `discord_redirect_uri`, `weblink_github_token` (fine-grained PAT scoped to `BentoBoxWorld/weblink` with `contents: write` + `pull-requests: write`), and the blog-related keys `blog_author_discord_ids`, `discord_blog_webhook_url`, `blog_base_url`, `cookie_domain`.
0 commit comments