Blog & site pages

The public site has marketing pages, an AI-written blog and the SEO endpoints Google Search Console needs.

Pages

PathWhat
/featuresEvery module explained (profiles, publisher, store, FiveM, Discord, automations, AI, sponsors, ingest, trust & admin)
/for/streamers, /for/fivem-servers, /for/communities, /for/brandsAudience landing pages with benefits, steps and CTAs
/about, /faq, /contactAbout, FAQ (with FAQPage JSON-LD), contact form
/blog, /blog/{slug}, /blog/category/{category}, /blog/tag/{tag}, /blog/feed.xmlBlog list, post (with BlogPosting JSON-LD and Open Graph), filters, RSS 2.0
/sitemap.xml, /robots.txtSitemap 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:

  1. Takes the next queued topic from blog_topics (manual topics first). The queue is seeded from the starter bank in App\Modules\Blog\Services\BlogTopics and, once that is used up, refilled by asking the AI for ten more topics that do not overlap existing titles.
  2. Asks the configured AIProvider (prompt blog.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.
  3. 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.
  4. Records the outcome in blog_runs (ok, skipped, failed with 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

  1. Add the property https://krunc.com (URL-prefix). Verify with the DNS TXT record or by adding the HTML tag Google gives you to resources/views/layouts/app.php inside <head>.
  2. 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 with lastmod, and is cached for 30 minutes.
  3. robots.txt already references the sitemap and disallows /dashboard, /admin, /api/, /webhooks/, /hooks/, auth pages, overlay and tracking URLs.
  4. Blog posts include canonical URLs, Open Graph tags and BlogPosting JSON-LD; the FAQ page includes FAQPage JSON-LD.