[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Content Versioning System

Build a Content Versioning System

Build a content versioning system with full revision history, diff visualization, branch-and-merge workflows, scheduled publishing, and rollback capabilities for CMS content.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

Rosa manages content at a 20-person media company publishing 50 articles daily. Writers accidentally overwrite each other's edits. Nobody knows who changed what or when. Rolling back a bad edit means restoring a full database backup — taking the site down for 20 minutes. Editors want to preview changes before publishing. Legal needs an audit trail of every content modification for compliance. They need git-like versioning for content: every change tracked, diffs visible, branches for drafts, scheduled publishing, and instant rollback.

Step 1: Build the Versioning Engine

typescript
// src/versioning/engine.ts — Content versioning with diffs, branches, and scheduled publishing
import { pool } from "../db";
import { Redis } from "ioredis";
import { createHash, randomBytes } from "node:crypto";

const redis = new Redis(process.env.REDIS_URL!);

interface ContentVersion {
  id: string;
  contentId: string;
  version: number;
  branch: string;
  title: string;
  body: string;
  metadata: Record<string, any>;
  checksum: string;
  authorId: string;
  authorName: string;
  message: string;          // commit message describing what changed
  parentVersionId: string | null;
  status: "draft" | "review" | "approved" | "published" | "archived";
  scheduledAt: string | null;
  createdAt: string;
}

interface ContentDiff {
  additions: number;
  deletions: number;
  changes: Array<{ type: "add" | "remove" | "change"; line: number; content: string }>;
}

// Save new version (auto-increments version number per branch)
export async function saveVersion(params: {
  contentId: string;
  branch?: string;
  title: string;
  body: string;
  metadata?: Record<string, any>;
  authorId: string;
  message: string;
}): Promise<ContentVersion> {
  const branch = params.branch || "main";

  // Get latest version on this branch
  const { rows: [latest] } = await pool.query(
    `SELECT * FROM content_versions WHERE content_id = $1 AND branch = $2 ORDER BY version DESC LIMIT 1`,
    [params.contentId, branch]
  );

  const version = latest ? latest.version + 1 : 1;
  const id = `cv-${randomBytes(6).toString("hex")}`;
  const checksum = createHash("sha256").update(params.title + params.body).digest("hex").slice(0, 12);

  const { rows: [author] } = await pool.query("SELECT name FROM users WHERE id = $1", [params.authorId]);

  const newVersion: ContentVersion = {
    id, contentId: params.contentId, version, branch,
    title: params.title, body: params.body,
    metadata: params.metadata || {},
    checksum, authorId: params.authorId,
    authorName: author?.name || "Unknown",
    message: params.message,
    parentVersionId: latest?.id || null,
    status: "draft",
    scheduledAt: null,
    createdAt: new Date().toISOString(),
  };

  await pool.query(
    `INSERT INTO content_versions (id, content_id, version, branch, title, body, metadata, checksum, author_id, message, parent_version_id, status, created_at)
     VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'draft', NOW())`,
    [id, params.contentId, version, branch, params.title, params.body,
     JSON.stringify(newVersion.metadata), checksum, params.authorId,
     params.message, newVersion.parentVersionId]
  );

  await redis.del(`content:latest:${params.contentId}:${branch}`);
  return newVersion;
}

// Get diff between two versions
export async function getDiff(versionIdA: string, versionIdB: string): Promise<ContentDiff> {
  const { rows: [a] } = await pool.query("SELECT * FROM content_versions WHERE id = $1", [versionIdA]);
  const { rows: [b] } = await pool.query("SELECT * FROM content_versions WHERE id = $1", [versionIdB]);
  if (!a || !b) throw new Error("Version not found");

  const linesA = a.body.split("\n");
  const linesB = b.body.split("\n");
  const changes: ContentDiff["changes"] = [];
  let additions = 0, deletions = 0;

  const maxLen = Math.max(linesA.length, linesB.length);
  for (let i = 0; i < maxLen; i++) {
    if (i >= linesA.length) { changes.push({ type: "add", line: i + 1, content: linesB[i] }); additions++; }
    else if (i >= linesB.length) { changes.push({ type: "remove", line: i + 1, content: linesA[i] }); deletions++; }
    else if (linesA[i] !== linesB[i]) { changes.push({ type: "change", line: i + 1, content: linesB[i] }); additions++; deletions++; }
  }

  return { additions, deletions, changes };
}

// Merge branch into main
export async function mergeBranch(contentId: string, sourceBranch: string, userId: string): Promise<ContentVersion> {
  const { rows: [source] } = await pool.query(
    `SELECT * FROM content_versions WHERE content_id = $1 AND branch = $2 ORDER BY version DESC LIMIT 1`,
    [contentId, sourceBranch]
  );
  if (!source) throw new Error("Source branch not found");

  return saveVersion({
    contentId, branch: "main",
    title: source.title, body: source.body,
    metadata: JSON.parse(source.metadata),
    authorId: userId,
    message: `Merge branch '${sourceBranch}' into main`,
  });
}

// Instant rollback to any previous version
export async function rollback(contentId: string, targetVersionId: string, userId: string): Promise<ContentVersion> {
  const { rows: [target] } = await pool.query(
    "SELECT * FROM content_versions WHERE id = $1 AND content_id = $2",
    [targetVersionId, contentId]
  );
  if (!target) throw new Error("Target version not found");

  return saveVersion({
    contentId, branch: "main",
    title: target.title, body: target.body,
    metadata: JSON.parse(target.metadata),
    authorId: userId,
    message: `Rollback to version ${target.version}`,
  });
}

// Schedule version for future publishing
export async function schedulePublish(versionId: string, publishAt: string): Promise<void> {
  await pool.query(
    "UPDATE content_versions SET scheduled_at = $2, status = 'approved' WHERE id = $1",
    [versionId, publishAt]
  );
  const ttl = Math.ceil((new Date(publishAt).getTime() - Date.now()) / 1000);
  if (ttl > 0) {
    await redis.setex(`content:scheduled:${versionId}`, ttl, "publish");
  }
}

// Publish version (make live, archive previous)
export async function publish(versionId: string): Promise<void> {
  const { rows: [version] } = await pool.query("SELECT * FROM content_versions WHERE id = $1", [versionId]);
  if (!version) throw new Error("Version not found");

  await pool.query(
    "UPDATE content_versions SET status = 'archived' WHERE content_id = $1 AND status = 'published'",
    [version.content_id]
  );
  await pool.query("UPDATE content_versions SET status = 'published' WHERE id = $1", [versionId]);
  await redis.del(`content:published:${version.content_id}`);
}

// Get full version history for content
export async function getHistory(contentId: string, branch?: string): Promise<ContentVersion[]> {
  let sql = "SELECT * FROM content_versions WHERE content_id = $1";
  const params: any[] = [contentId];
  if (branch) { sql += " AND branch = $2"; params.push(branch); }
  sql += " ORDER BY version DESC LIMIT 100";
  const { rows } = await pool.query(sql, params);
  return rows;
}

Results

  • Full audit trail — every edit tracked with author, timestamp, and commit message; legal compliance satisfied; no more "who changed the headline?"
  • Instant rollback: 20 min → 1 click — bad edit reverted by creating a new version from old content; no database restore; site stays up
  • Branch workflows — writer creates "redesign" branch, edits freely, editor reviews diff, merges when ready; no overwriting live content
  • Scheduled publishing — article set to publish at 9 AM Monday; editor approves Friday; version auto-publishes on schedule
  • Visual diffs — editor sees exactly what changed: +12 lines added, -3 removed; reviews in minutes instead of re-reading entire article