Skip to content

Content Submission: Go Design Patterns #43

Content Submission: Go Design Patterns

Content Submission: Go Design Patterns #43

name: Payload-Based Content PR Handler
on:
pull_request:
types: [opened, edited, synchronize]
paths:
- '.github/content-payloads/**'
- 'base.json'
pull_request_review:
types: [submitted]
jobs:
validate-payload-pr:
runs-on: ubuntu-latest
if: github.event.pull_request.draft == false
permissions:
contents: write
issues: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
persist-credentials: true
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: |
npm install -g marked js-yaml
- name: Check if this is a payload PR
run: |
echo "🔍 Checking if this is a payload-based PR..."
# Check if payloads directory exists and has files
if [ ! -d ".github/content-payloads" ]; then
echo "ℹ️ No payloads directory found - this is not a payload PR"
echo "SKIP_PAYLOAD_VALIDATION=true" >> $GITHUB_ENV
exit 0
fi
payload_files=$(find .github/content-payloads -name "*.json" 2>/dev/null | wc -l)
if [ "$payload_files" -eq 0 ]; then
echo "ℹ️ No payload files found - this is not a payload PR"
echo "SKIP_PAYLOAD_VALIDATION=true" >> $GITHUB_ENV
exit 0
fi
echo "✅ Payload files found - proceeding with validation"
echo "SKIP_PAYLOAD_VALIDATION=false" >> $GITHUB_ENV
- name: Handle non-payload PR
if: env.SKIP_PAYLOAD_VALIDATION == 'true'
run: |
echo "ℹ️ This is not a payload-based PR - skipping payload validation"
echo "✅ Workflow completed successfully for non-payload PR"
- name: Validate payload files
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "🔍 Validating payload files..."
python3 << 'EOF'
import json
import os
import sys
import subprocess
payloads_dir = '.github/content-payloads'
if not os.path.exists(payloads_dir):
print("❌ No payloads directory found")
sys.exit(1)
# Get only changed files in this PR
try:
result = subprocess.run(['git', 'diff', '--name-only', 'HEAD~1', 'HEAD'],
capture_output=True, text=True, check=True)
changed_files = result.stdout.strip().split('\n')
payload_files = [f for f in changed_files
if f.startswith('.github/content-payloads/') and f.endswith('.json')]
except subprocess.CalledProcessError:
# Fallback: process all files if not in PR context
payload_files = [f for f in os.listdir(payloads_dir) if f.endswith('.json')]
payload_files = [os.path.join(payloads_dir, f) for f in payload_files]
if not payload_files:
print("❌ No new payload files found in this PR")
sys.exit(1)
print(f"📋 Found {len(payload_files)} new payload file(s) in this PR")
for payload_file in payload_files:
try:
# Handle both relative and absolute paths
if payload_file.startswith(payloads_dir):
file_path = payload_file
else:
file_path = os.path.join(payloads_dir, payload_file)
with open(file_path, 'r') as f:
payload = json.load(f)
# Validate required fields
required_fields = ['version', 'type', 'metadata', 'content']
for field in required_fields:
if field not in payload:
print(f"❌ Missing required field '{field}' in {payload_file}")
sys.exit(1)
# Validate metadata
required_metadata = ['title', 'category', 'subtopic']
for field in required_metadata:
if field not in payload['metadata']:
print(f"❌ Missing required metadata field '{field}' in {payload_file}")
sys.exit(1)
# Validate content
required_content = ['id', 'title', 'description']
for field in required_content:
if field not in payload['content']:
print(f"❌ Missing required content field '{field}' in {payload_file}")
sys.exit(1)
print(f"✅ Validated payload: {payload_file}")
except Exception as e:
print(f"❌ Error validating {payload_file}: {e}")
sys.exit(1)
print("✅ All payload files are valid")
EOF
- name: Validate base configuration
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "🔍 Validating base configuration..."
if [ -f "base.json" ]; then
python3 -m json.tool base.json > /dev/null
echo "✅ Base configuration is valid"
else
echo "❌ Base configuration not found"
exit 1
fi
- name: Test content merge engine
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "🔄 Testing content merge engine..."
node scripts/merge-content.js
if [ -f "content-config.json" ]; then
echo "✅ Content merge engine generated configuration successfully"
else
echo "❌ Content merge engine failed to generate configuration"
exit 1
fi
- name: Validate GitHub repository access
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "🔍 Validating GitHub repository access..."
python3 << 'EOF'
import json
import requests
import sys
import os
import subprocess
payloads_dir = '.github/content-payloads'
# Get only changed files in this PR
try:
result = subprocess.run(['git', 'diff', '--name-only', 'HEAD~1', 'HEAD'],
capture_output=True, text=True, check=True)
changed_files = result.stdout.strip().split('\n')
payload_files = [f for f in changed_files
if f.startswith('.github/content-payloads/') and f.endswith('.json')]
except subprocess.CalledProcessError:
# Fallback: process all files if not in PR context
payload_files = [f for f in os.listdir(payloads_dir) if f.endswith('.json')]
payload_files = [os.path.join(payloads_dir, f) for f in payload_files]
for payload_file in payload_files:
# Handle both relative and absolute paths
if payload_file.startswith(payloads_dir):
file_path = payload_file
else:
file_path = os.path.join(payloads_dir, payload_file)
with open(file_path, 'r') as f:
payload = json.load(f)
if 'repo' in payload['content'] and 'path' in payload['content']:
repo = payload['content']['repo']
path = payload['content']['path']
# Skip validation if repo or path is empty
if not repo or not path:
print(f"⚠️ Skipping validation for {payload_file}: empty repo or path")
continue
# Test GitHub API access
url = f"https://api.github.com/repos/{repo}"
try:
response = requests.get(url, timeout=10)
if response.status_code == 200:
print(f"✅ Repository accessible: {repo}")
else:
print(f"❌ Repository not accessible: {repo} (Status: {response.status_code})")
sys.exit(1)
except requests.RequestException as e:
print(f"❌ Error checking repository {repo}: {e}")
sys.exit(1)
# Test file access
raw_url = f"https://raw.githubusercontent.com/{repo}/main/{path}"
try:
file_response = requests.head(raw_url, timeout=10)
if file_response.status_code == 200:
print(f"✅ File accessible: {repo}/{path}")
else:
print(f"❌ File not accessible: {repo}/{path} (Status: {file_response.status_code})")
sys.exit(1)
except requests.RequestException as e:
print(f"❌ Error checking file {repo}/{path}: {e}")
sys.exit(1)
else:
print(f"⚠️ Skipping repository validation for {payload_file}: no repo/path specified")
EOF
- name: Generate site preview
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "🏗️ Generating site preview with merged content..."
node scripts/update-navigation.js
echo "✅ Site preview generated successfully"
# Verify the HTML files were updated with new content
echo "🔍 Verifying updated HTML files..."
node -e "
const fs = require('fs');
const config = JSON.parse(fs.readFileSync('content-config.json', 'utf8'));
// Get all new content titles from the merged config
const newContentTitles = [];
for (const category of Object.values(config.categories)) {
for (const subtopic of Object.values(category.subtopics)) {
for (const content of subtopic.content) {
if (content.status === 'active') {
newContentTitles.push(content.title);
}
}
}
}
// Check if any HTML files contain the new content
const htmlFiles = ['index.html', 'kubernetes.html', 'algorithms.html', 'networking.html', 'databases.html', 'microservices.html', 'system-design.html'];
let foundContent = 0;
for (const htmlFile of htmlFiles) {
if (fs.existsSync(htmlFile)) {
const content = fs.readFileSync(htmlFile, 'utf8');
for (const title of newContentTitles) {
if (content.includes(title)) {
foundContent++;
break;
}
}
}
}
if (foundContent > 0) {
console.log('✅ HTML files updated with new content');
} else {
console.log('❌ HTML files not updated properly');
process.exit(1);
}
"
- name: Commit generated files
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
run: |
echo "📝 Committing generated HTML files to PR..."
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Check if there are changes to commit
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
else
git add *.html content-config.json
git commit -m "chore: Auto-generate HTML files with new content
- Generated by payload workflow
- Includes updated navigation and content
- Auto-commit for Vercel preview deployment"
echo "🔄 Pushing generated files to PR branch..."
git push origin HEAD:${{ github.head_ref }}
fi
- name: Upload preview artifacts
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
uses: actions/upload-artifact@v4
with:
name: site-preview
path: |
index.html
kubernetes.html
algorithms.html
networking.html
databases.html
microservices.html
system-design.html
content-config.json
retention-days: 7
- name: Comment on PR
if: env.SKIP_PAYLOAD_VALIDATION != 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
try {
// Read the generated content config
const config = JSON.parse(fs.readFileSync('content-config.json', 'utf8'));
// Count new payloads in this PR
const { execSync } = require('child_process');
let payloadCount = 0;
try {
const changedFiles = execSync('git diff --name-only HEAD~1 HEAD', { encoding: 'utf8' });
const payloadFiles = changedFiles.trim().split('\n')
.filter(file => file.startsWith('.github/content-payloads/') && file.endsWith('.json'));
payloadCount = payloadFiles.length;
} catch (error) {
// Fallback: count all payload files
const payloadsDir = '.github/content-payloads';
if (fs.existsSync(payloadsDir)) {
const files = fs.readdirSync(payloadsDir);
payloadCount = files.filter(f => f.endsWith('.json')).length;
}
}
// Generate statistics
let totalContent = 0;
let totalVisualizations = 0;
let totalGuides = 0;
let categoriesWithContent = 0;
for (const category of Object.values(config.categories)) {
if (Object.keys(category.subtopics).length > 0) {
categoriesWithContent++;
for (const subtopic of Object.values(category.subtopics)) {
for (const contentItem of subtopic.content) {
if (contentItem.status === 'active') {
totalContent++;
if (contentItem.type === 'visualization') {
totalVisualizations++;
} else {
totalGuides++;
}
}
}
}
}
}
const comment = `## 🎉 Payload-Based Content Validation Successful!
Your content payload(s) have been validated and merged successfully!
### 📊 Impact Summary
- **Payload files processed**: ${payloadCount}
- **Total content items**: ${totalContent}
- **Visualizations**: ${totalVisualizations}
- **Guides**: ${totalGuides}
- **Active categories**: ${categoriesWithContent}
- **Configuration version**: ${config.version}
### ✅ Validation Results
- Payload file structure is valid
- Base configuration is valid
- Content merge engine executed successfully
- GitHub repository access confirmed
- File paths are accessible
- Site preview generated successfully
### 🏗️ Site Preview Generated
The workflow has generated a complete site preview with your changes:
- Updated navigation structure
- Modified category pages
- Updated statistics
- Preview artifacts uploaded for review
### 🚀 Next Steps
Once this PR is merged, the content will be automatically:
1. Merged into the base configuration
2. Integrated into the site navigation
3. Made accessible through the template renderer
4. Included in site statistics
5. Deployed live immediately
**Review the generated preview artifacts to see exactly how the site will look!** 🎯`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
});
} catch (error) {
console.error('Error creating comment:', error);
}
auto-merge-ready:
runs-on: ubuntu-latest
needs: validate-payload-pr
if: github.event.pull_request.draft == false && github.event.review.state == 'approved'
permissions:
contents: read
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Auto-merge PR
uses: actions/github-script@v7
with:
script: |
const { owner, repo, number } = context.issue;
// Check if PR is ready for merge
const pr = await github.rest.pulls.get({
owner,
repo,
pull_number: number
});
if (pr.data.mergeable && pr.data.mergeable_state === 'clean') {
await github.rest.pulls.merge({
owner,
repo,
pull_number: number,
merge_method: 'squash'
});
console.log('✅ PR merged successfully!');
} else {
console.log('❌ PR is not ready for merge');
}