Blog / Knowledge Base

Content publishing system for security thought leadership, threat research, and platform update announcements with in-memory post storage and client-side Markdown rendering.

Overview

The Blog module provides a content publishing system for ThreatOps marketing and knowledge-base articles. As a SOCaaS platform competing in the enterprise security market, ThreatOps needs a vehicle for thought leadership: publishing research on SOC automation, compliance frameworks, SIEM integration strategies, and platform announcements. This content builds brand authority, drives organic discovery, and gives prospects substantive reasons to engage with the platform before becoming customers.

The module is deliberately lightweight. Posts are stored in-memory as JSON, seeded with seven substantive cybersecurity articles at startup, and served through a REST API that the Next.js frontend consumes via two client-side rendered pages: a grid index and an individual post detail view.

Design Decision: In-Memory Storage

In-memory storage was chosen for the initial build to avoid schema migrations and keep the module self-contained. The module comment explicitly notes that a future iteration can persist to Postgres without changing the API contract. All state lives in a module-level dict populated from SEED_POSTS at import time.

What Was Proposed

What's Built Complete

Architecture

Self-Contained Module

The Blog module is entirely self-contained with no database dependencies. The backend router stores all state in a module-level _posts dictionary populated from SEED_POSTS at import time. The frontend consists of two Next.js App Router pages -- an index grid and a slug-based detail page -- both using "use client" with useState, useEffect, and useCallback. Data fetching uses the shared api client from @/lib/api-client.

Backend (FastAPI)                    Frontend (Next.js)
+-------------------------------+    +-------------------------------+
| blog.py                       |    | /blog (page.tsx)              |
|                               |    |   Fetch all posts             |
| SEED_POSTS -> _posts dict     |    |   Derive category list        |
| _published_posts() helper     |    |   Category filter pills       |
|                               |    |   Post card grid (1/2/3 col)  |
| GET  /posts      -> list      |<---|   Loading / Error / Empty     |
| GET  /posts/{slug} -> detail  |<---|                               |
| POST /posts      -> create    |    | /blog/[slug] (page.tsx)       |
| PATCH /posts/{id} -> update   |    |   Fetch single post by slug   |
+-------------------------------+    |   renderMarkdown() -> HTML    |
                                     |   dangerouslySetInnerHTML      |
                                     |   Tag chips, author, date      |
                                     +-------------------------------+

Routing

Frontend Routes
/blog
/blog/[slug]
API Prefix
/api/v1/blog

Prerequisites

Data Model

Blog Post (In-Memory Dict / Pydantic Schema)

FieldTypeDescription
idstr (UUID4)Auto-generated on create. Used as PATCH route parameter.
titlestrPost title. Displayed in cards (2-line clamp) and as H1 on detail page.
slugstrURL-safe identifier. Used as Next.js dynamic route segment and GET lookup key.
excerptstr2-3 sentence summary shown on index cards (3-line clamp).
contentstrFull article body with Markdown syntax (## headings, **bold**, `code`, - lists).
categorystrFree-form string. Known values: SOC Operations, Compliance, Threat Intelligence, Platform Updates.
authorstrDefault: "ThreatOps Team". Shown with User icon on cards and detail header.
read_timestrHuman-readable estimate (e.g., "6 min read"). Displayed in blue on detail page.
cover_imageOptional[str]URL to cover image. All seed posts are null. Wired for future image hosting.
tagslist[str]Keyword tags. Rendered as grey badge chips on the detail page.
publishedboolControls visibility. Only true entries returned by list/get endpoints. Toggleable via PATCH.
published_atstr (ISO 8601)Determines sort order on list endpoint (descending, newest first).
updated_atstr (ISO 8601)Auto-refreshed to current UTC on every PATCH call.

Pydantic Schemas

SchemaDirectionNotes
BlogPostCreatePOST request bodyRequired: title, slug, excerpt, content, category. Defaults: author="ThreatOps Team", read_time="5 min read", tags=[]
BlogPostUpdatePATCH request bodyAll fields optional. Includes published boolean for draft/publish toggle.
BlogPostResponseAll responsesFull post data including auto-generated id, published flag, published_at, updated_at timestamps.

API Endpoints

MethodEndpointDescription
GET/postsList all published posts (newest first). Query params: category (case-insensitive filter), skip (int, default 0), limit (int, default 20).
GET/posts/{slug}Get single published post by URL slug. Returns 404 if not found or unpublished.
POST/postsCreate new post. Auto-generates UUID, sets published=true, timestamps published_at and updated_at. Returns 201.
PATCH/posts/{post_id}Partially update post by UUID (not slug). Only supplied fields changed. Auto-refreshes updated_at. Returns 404 if unknown.

Seed Content

Seven full-length cybersecurity articles (600-900 words each) are seeded at module load from SEED_POSTS in blog.py. All have published=true with fixed published_at dates spanning September 2025 through January 2026:

CategoryTitleRead TimeDate
SOC OperationsThe Evolution of SOC Operations: Why Automation is No Longer Optional6 minDec 2025
ComplianceAchieving FedRAMP High Compliance in Your Security Operations7 minNov 2025
Threat IntelligenceMulti-SIEM Strategy: Integrating Sentinel, Splunk, and Beyond6 minNov 2025
SOC OperationsAI-Powered Alert Triage: Reducing Alert Fatigue by 80%6 minOct 2025
ComplianceBuilding a Compliance-First Security Operations Center7 minOct 2025
Threat IntelligenceUEBA: Detecting Insider Threats Before They Strike6 minSep 2025
Platform UpdatesThreatOps Platform Update: Real-Time Alert Streaming & SOAR Playbooks5 minJan 2026

UI Description

Blog Index Page (/blog)

Header with BookOpen icon (blue) and "ThreatOps Blog" H1 title, subtitle "Security insights, threat research, and platform updates" in slate-500. Error banner (red border card) conditionally rendered when the API call throws. Category filter bar with "All" pill plus dynamically-derived category pills from fetched posts. Active pill uses blue fill; inactive pills are white with slate border. Responsive post card grid (1/2/3 columns) where each card is a Next.js Link to /blog/{slug}. Card structure: color-coded category badge, title (2-line clamp, hover turns blue), excerpt (3-line clamp), footer divider with author, publish date, and read time. Loading state shows centered Loader2 spinner. Empty state shows "No posts found" centered text.

Post Detail Page (/blog/[slug])

Constrained to max-w-3xl for comfortable reading width. "Back to Blog" link with ArrowLeft icon routes to /blog. Post header block: category badge (blue), H1 title (text-3xl bold), meta row with User icon + author, Clock icon + formatted long date, read time in blue. Tag chips rendered as grey badges with Tag icon. Horizontal rule divider. Article body rendered via dangerouslySetInnerHTML from renderMarkdown(post.content) which applies regex replacements for H1/H2/H3 headings, bold, italic, inline code, list items, and paragraph breaks. Error/not-found state shows back link plus red error card.

Category Color Mapping

CategoryStyle
Threat IntelligenceRed background, red text, red border
Detection EngineeringBlue background, blue text, blue border
Incident ResponseOrange background, orange text, orange border
Cloud SecurityBlue background, blue text, blue border
ComplianceEmerald background, emerald text, emerald border
Vulnerability ManagementYellow background, yellow text, yellow border
SOC OperationsPurple background, purple text, purple border
Default (others)Slate background, slate text, slate border

Source Files

ComponentPath
API Routerplatform/api/app/routers/blog.py
Blog Index Pageplatform/frontend/src/app/blog/page.tsx
Post Detail Pageplatform/frontend/src/app/blog/[slug]/page.tsx