Blog & site pages
The public site has marketing pages, an AI-written blog and the SEO endpoints Google Search Console needs.
Pages
| Path | What |
|---|---|
/features | Every module explained (profiles, publisher, store, FiveM, Discord, automations, AI, sponsors, ingest, trust & admin) |
/for/streamers, /for/fivem-servers, /for/communities, /for/brands | Audience landing pages with benefits, steps and CTAs |
/about, /faq, /contact | About, FAQ (with FAQPage JSON-LD), contact form |
/blog, /blog/{slug}, /blog/category/{category}, /blog/tag/{tag}, /blog/feed.xml | Blog list, post (with BlogPosting JSON-LD and Open Graph), filters, RSS 2.0 |
/sitemap.xml, /robots.txt | Sitemap of every public URL; robots with dashboard/admin/API paths disallowed |
Views live in app/Modules/Home/views/ (marketing) and app/Modules/Blog/views/ (blog). Nav and footer links are in resources/views/partials/.
Contact form
POST /contact validates name, email, topic and message, drops submissions that fill the hidden honeypot field, is throttled per IP, and stores the message in contact_messages. Platform admins read and triage them at /admin/messages (new / handled / spam). Set CONTACT_EMAIL in .env to also receive each message by email through the configured EmailProvider.
Blog
Posts are platform-level rows in blog_posts (Markdown body rendered to HTML by App\Core\Markdown, which escapes everything first so AI output cannot inject markup; links are limited to http(s), mailto and site-relative paths).
Automatic writing. The scheduler task blog.autopublish runs every BLOG_AUTOPUBLISH_HOURS (default 48 — every other day). Each run:
- Takes the next queued topic from
blog_topics(manual topics first). The queue is seeded from the starter bank inApp\Modules\Blog\Services\BlogTopicsand, once that is used up, refilled by asking the AI for ten more topics that do not overlap existing titles. - Asks the configured
AIProvider(promptblog.post.v1) for a 1,000–1,500-word post as JSON: title, slug, excerpt, meta description, tags and Markdown body. The prompt lists the only product features and internal paths the writer may mention, and the recent titles it must not repeat. - Rejects the result if it is incomplete, shorter than 400 words or contains HTML; otherwise saves and publishes it, marks the topic used and writes an audit entry.
- Records the outcome in
blog_runs(ok,skipped,failedwith the real error) — shown on/admin/blog.
It never publishes mock content. With ANTHROPIC_API_KEY blank the provider is the mock, every run is recorded as skipped with the reason, and the admin page says so. The feature flag blog.autopublish (missing row = on) pauses the scheduler without touching the key.
Admin (/admin/blog, platform admins): posts list, Generate & publish now (synchronous, so you see the real result or error), Write manually (Markdown editor with status, category, slug, tags, cover, author, meta description), topic queue (add, skip, "Draft" a specific topic without publishing, ask the AI for ten more), recent runs and the next automatic run time.
Posts carry ai_generated, ai_model and token counts. Public pages show "Written with AI, reviewed by the team" on AI posts.
Google Search Console
- Add the property
https://krunc.com(URL-prefix). Verify with the DNS TXT record or by adding the HTML tag Google gives you toresources/views/layouts/app.phpinside<head>. - Sitemaps → add
https://krunc.com/sitemap.xml. It lists the marketing pages, docs, published blog posts and categories, public creator profiles (/@handle), public communities (/c/slug), public servers and storefronts withlastmod, and is cached for 30 minutes. robots.txtalready references the sitemap and disallows/dashboard,/admin,/api/,/webhooks/,/hooks/, auth pages, overlay and tracking URLs.- Blog posts include canonical URLs, Open Graph tags and
BlogPostingJSON-LD; the FAQ page includesFAQPageJSON-LD.