fix(security): secure archived documents endpoint and reorganize docs UI
Security: - Add authentication to /api/documents/archived endpoint (admin-only) - Prevent public exposure of 108 archived/internal documents Documentation UI: - Remove duplicate hardcoded Resources section from docs.html - Add Resources category to docs-app.js for implementation guides - Move 3 implementation guides from Getting Started to Resources - Move Glossary from Technical Reference to Getting Started - Set Research & Theory section to collapsed by default - Update service worker cache version to 0.1.4 Migration Scripts: - Add scripts for document category reorganization - Add scripts for research document migration to production - Add scripts for glossary verification and comparison Files changed: - public/docs.html: Remove duplicate Resources section - public/js/docs-app.js: Add Resources category, collapse Research - public/service-worker.js: Bump cache to v0.1.4 - src/routes/documents.routes.js: Secure /archived endpoint - scripts/*: Add 10 migration/diagnostic scripts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
043d14a499
commit
be53ab36f8
14 changed files with 826 additions and 91 deletions
|
|
@ -534,49 +534,6 @@
|
|||
<div class="text-sm text-gray-500">Loading...</div>
|
||||
</div>
|
||||
|
||||
<!-- Resources Section -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="font-semibold text-gray-900 mb-3">Resources</h3>
|
||||
<div class="space-y-2">
|
||||
<!-- Research Papers -->
|
||||
<a href="/downloads/structural-governance-for-agentic-ai-tractatus-inflection-point.pdf"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 p-2 rounded-lg hover:bg-blue-50 transition group">
|
||||
<svg class="w-4 h-4 text-blue-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-900 group-hover:text-blue-700 transition">Research Paper (Full)</div>
|
||||
<div class="text-xs text-gray-500">Tractatus Inflection Point Study</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/downloads/executive-summary-tractatus-inflection-point.pdf"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 p-2 rounded-lg hover:bg-blue-50 transition group">
|
||||
<svg class="w-4 h-4 text-blue-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"/>
|
||||
</svg>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-900 group-hover:text-blue-700 transition">Executive Summary</div>
|
||||
<div class="text-xs text-gray-500">5-minute read, key findings</div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="/downloads/ai-governance-business-case-template.pdf"
|
||||
target="_blank"
|
||||
class="flex items-center gap-2 p-2 rounded-lg hover:bg-amber-50 transition group">
|
||||
<svg class="w-4 h-4 text-amber-600 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||
</svg>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-900 group-hover:text-amber-700 transition">Business Case Template</div>
|
||||
<div class="text-xs text-gray-500">Assessment guide for organizations</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- GitHub Section -->
|
||||
<div class="mt-6 pt-6 border-t border-gray-200">
|
||||
<h3 class="font-semibold text-gray-900 mb-3 flex items-center gap-2">
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ const CATEGORIES = {
|
|||
'getting-started': {
|
||||
label: '📚 Getting Started',
|
||||
icon: '📚',
|
||||
description: 'Introduction, core concepts, and implementation guides',
|
||||
description: 'Introduction, core concepts, and glossary',
|
||||
order: 1,
|
||||
color: 'blue',
|
||||
bgColor: 'bg-blue-50',
|
||||
|
|
@ -20,22 +20,33 @@ const CATEGORIES = {
|
|||
textColor: 'text-blue-700',
|
||||
collapsed: false
|
||||
},
|
||||
'resources': {
|
||||
label: '📖 Resources',
|
||||
icon: '📖',
|
||||
description: 'Implementation guides and reference materials',
|
||||
order: 2,
|
||||
color: 'amber',
|
||||
bgColor: 'bg-amber-50',
|
||||
borderColor: 'border-l-4 border-amber-500',
|
||||
textColor: 'text-amber-700',
|
||||
collapsed: false
|
||||
},
|
||||
'research-theory': {
|
||||
label: '🔬 Research & Theory',
|
||||
icon: '🔬',
|
||||
description: 'Research papers, case studies, theoretical foundations',
|
||||
order: 2,
|
||||
order: 3,
|
||||
color: 'purple',
|
||||
bgColor: 'bg-purple-50',
|
||||
borderColor: 'border-l-4 border-purple-500',
|
||||
textColor: 'text-purple-700',
|
||||
collapsed: false // Expanded to show Working Paper v0.1
|
||||
collapsed: true
|
||||
},
|
||||
'technical-reference': {
|
||||
label: '🔌 Technical Reference',
|
||||
icon: '🔌',
|
||||
description: 'API documentation, code examples, architecture',
|
||||
order: 3,
|
||||
order: 4,
|
||||
color: 'green',
|
||||
bgColor: 'bg-green-50',
|
||||
borderColor: 'border-l-4 border-green-500',
|
||||
|
|
@ -46,7 +57,7 @@ const CATEGORIES = {
|
|||
label: '🎓 Advanced Topics',
|
||||
icon: '🎓',
|
||||
description: 'Value pluralism, organizational theory, advanced concepts',
|
||||
order: 4,
|
||||
order: 5,
|
||||
color: 'teal',
|
||||
bgColor: 'bg-teal-50',
|
||||
borderColor: 'border-l-4 border-teal-500',
|
||||
|
|
@ -57,7 +68,7 @@ const CATEGORIES = {
|
|||
label: '💼 Business & Leadership',
|
||||
icon: '💼',
|
||||
description: 'Business cases, ROI analysis, executive briefs',
|
||||
order: 5,
|
||||
order: 6,
|
||||
color: 'pink',
|
||||
bgColor: 'bg-pink-50',
|
||||
borderColor: 'border-l-4 border-pink-500',
|
||||
|
|
@ -172,13 +183,8 @@ async function loadDocuments() {
|
|||
const data = await response.json();
|
||||
documents = data.documents || [];
|
||||
|
||||
// Fetch archived documents
|
||||
const archivedResponse = await fetch('/api/documents/archived');
|
||||
const archivedData = await archivedResponse.json();
|
||||
const archivedDocuments = archivedData.documents || [];
|
||||
|
||||
const listEl = document.getElementById('document-list');
|
||||
if (documents.length === 0 && archivedDocuments.length === 0) {
|
||||
if (documents.length === 0) {
|
||||
listEl.innerHTML = '<div class="text-sm text-gray-500">No documents available</div>';
|
||||
return;
|
||||
}
|
||||
|
|
@ -228,40 +234,6 @@ async function loadDocuments() {
|
|||
`;
|
||||
});
|
||||
|
||||
// Add Archives section if there are archived documents
|
||||
if (archivedDocuments.length > 0) {
|
||||
html += `
|
||||
<div class="category-section mb-4 mt-8" data-category="archives">
|
||||
<button class="category-toggle w-full flex items-center justify-between px-3 py-3 text-sm font-bold text-gray-600 bg-gray-50 border-l-4 border-gray-400 rounded-r hover:shadow-md transition-all"
|
||||
data-category="archives"
|
||||
data-collapsed="true">
|
||||
<span class="flex items-center gap-2">
|
||||
<span class="category-icon">📦</span>
|
||||
<span>Archives</span>
|
||||
<span class="text-xs font-normal text-gray-500">(${archivedDocuments.length} documents)</span>
|
||||
</span>
|
||||
<svg class="category-arrow w-5 h-5 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="category-docs mt-2 pl-2" data-category="archives" data-collapsed="true">
|
||||
`;
|
||||
|
||||
// Render archived documents
|
||||
archivedDocuments.forEach(doc => {
|
||||
html += renderDocLink(doc, false);
|
||||
// Add archive note if available
|
||||
if (doc.archiveNote) {
|
||||
html += `<div class="text-xs text-gray-500 italic pl-6 mb-2">${doc.archiveNote}</div>`;
|
||||
}
|
||||
});
|
||||
|
||||
html += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
listEl.innerHTML = html;
|
||||
|
||||
// Apply collapsed state to categories (CSP-compliant - no inline styles)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
* - PWA functionality
|
||||
*/
|
||||
|
||||
const CACHE_VERSION = '0.1.1';
|
||||
const CACHE_VERSION = '0.1.4';
|
||||
const CACHE_NAME = `tractatus-v${CACHE_VERSION}`;
|
||||
const VERSION_CHECK_INTERVAL = 3600000; // 1 hour in milliseconds
|
||||
|
||||
|
|
|
|||
48
scripts/check-glossary.js
Normal file
48
scripts/check-glossary.js
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* Check Glossary documents in production
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' GLOSSARY DOCUMENTS IN PRODUCTION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
const docs = await client.db(DB_NAME).collection('documents').find({
|
||||
slug: { $regex: 'glossary', $options: 'i' }
|
||||
}).project({
|
||||
slug: 1,
|
||||
title: 1,
|
||||
visibility: 1,
|
||||
category: 1,
|
||||
order: 1,
|
||||
updated_at: 1
|
||||
}).toArray();
|
||||
|
||||
if (docs.length === 0) {
|
||||
console.log('❌ No glossary documents found\n');
|
||||
} else {
|
||||
docs.forEach((doc, idx) => {
|
||||
console.log(`${idx + 1}. ${doc.title}`);
|
||||
console.log(` Slug: ${doc.slug}`);
|
||||
console.log(` Visibility: ${doc.visibility}`);
|
||||
console.log(` Category: ${doc.category || 'none'}`);
|
||||
console.log(` Order: ${doc.order || 'none'}`);
|
||||
console.log(` Updated: ${doc.updated_at || 'unknown'}`);
|
||||
console.log('');
|
||||
});
|
||||
}
|
||||
|
||||
await client.close();
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
97
scripts/compare-glossaries.js
Normal file
97
scripts/compare-glossaries.js
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/**
|
||||
* Compare Glossary documents in dev and production
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' COMPARING GLOSSARY DOCUMENTS');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// Check dev
|
||||
const devClient = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient.connect();
|
||||
|
||||
const devDocs = await devClient.db('tractatus_dev').collection('documents').find({
|
||||
slug: { $regex: 'glossary', $options: 'i' }
|
||||
}).project({
|
||||
slug: 1,
|
||||
title: 1,
|
||||
visibility: 1,
|
||||
category: 1,
|
||||
sections: 1
|
||||
}).toArray();
|
||||
|
||||
await devClient.close();
|
||||
|
||||
console.log(`📚 DEV Database: ${devDocs.length} glossary document(s)\n`);
|
||||
|
||||
devDocs.forEach((doc, idx) => {
|
||||
console.log(`${idx + 1}. ${doc.title}`);
|
||||
console.log(` Slug: ${doc.slug}`);
|
||||
console.log(` Visibility: ${doc.visibility}`);
|
||||
console.log(` Category: ${doc.category || 'none'}`);
|
||||
console.log(` Sections: ${doc.sections?.length || 0}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
// Check production
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
const prodClient = new MongoClient(MONGODB_URI);
|
||||
await prodClient.connect();
|
||||
|
||||
const prodDocs = await prodClient.db(DB_NAME).collection('documents').find({
|
||||
slug: { $regex: 'glossary', $options: 'i' }
|
||||
}).project({
|
||||
slug: 1,
|
||||
title: 1,
|
||||
visibility: 1,
|
||||
category: 1,
|
||||
sections: 1
|
||||
}).toArray();
|
||||
|
||||
await prodClient.close();
|
||||
|
||||
console.log(`📦 PRODUCTION Database: ${prodDocs.length} glossary document(s)\n`);
|
||||
|
||||
prodDocs.forEach((doc, idx) => {
|
||||
console.log(`${idx + 1}. ${doc.title}`);
|
||||
console.log(` Slug: ${doc.slug}`);
|
||||
console.log(` Visibility: ${doc.visibility}`);
|
||||
console.log(` Category: ${doc.category || 'none'}`);
|
||||
console.log(` Sections: ${doc.sections?.length || 0}`);
|
||||
console.log('');
|
||||
});
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' RECOMMENDATION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// Find the best one
|
||||
const allDocs = [...devDocs.map(d => ({...d, source: 'dev'})), ...prodDocs.map(d => ({...d, source: 'prod'}))];
|
||||
const best = allDocs.reduce((best, doc) => {
|
||||
const sectionsCount = doc.sections?.length || 0;
|
||||
const bestSections = best.sections?.length || 0;
|
||||
return sectionsCount > bestSections ? doc : best;
|
||||
}, allDocs[0]);
|
||||
|
||||
if (best) {
|
||||
console.log(`Best version: ${best.slug} (${best.source})`);
|
||||
console.log(` ${best.sections?.length || 0} sections`);
|
||||
console.log(` Currently: ${best.visibility}`);
|
||||
console.log('');
|
||||
|
||||
if (best.visibility !== 'public') {
|
||||
console.log('✅ Action: Unarchive this document and set visibility=public');
|
||||
} else {
|
||||
console.log('✅ Already public - no action needed');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
80
scripts/diagnose-research-migration.js
Normal file
80
scripts/diagnose-research-migration.js
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
/**
|
||||
* Diagnose research document migration issue
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' DIAGNOSING RESEARCH DOCUMENT MIGRATION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// 1. Check dev database
|
||||
console.log('1️⃣ Checking DEV database...\n');
|
||||
const devClient = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient.connect();
|
||||
const devDoc = await devClient.db('tractatus_dev').collection('documents').findOne({
|
||||
slug: 'tractatus-framework-research'
|
||||
});
|
||||
|
||||
if (devDoc) {
|
||||
console.log(` ✅ Found in dev: ${devDoc.title}`);
|
||||
console.log(` Category: ${devDoc.category}`);
|
||||
console.log(` Order: ${devDoc.order}`);
|
||||
console.log(` Sections: ${devDoc.sections?.length || 0}`);
|
||||
console.log(` Visibility: ${devDoc.visibility}`);
|
||||
} else {
|
||||
console.log(' ❌ NOT found in dev');
|
||||
}
|
||||
await devClient.close();
|
||||
|
||||
// 2. Check production database
|
||||
console.log('\n2️⃣ Checking PRODUCTION database...\n');
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI;
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
console.log(` URI: ${MONGODB_URI?.replace(/:[^:]*@/, ':***@')}`);
|
||||
console.log(` Database: ${DB_NAME}\n`);
|
||||
|
||||
const prodClient = new MongoClient(MONGODB_URI);
|
||||
await prodClient.connect();
|
||||
|
||||
const prodDoc = await prodClient.db(DB_NAME).collection('documents').findOne({
|
||||
slug: 'tractatus-framework-research'
|
||||
});
|
||||
|
||||
if (prodDoc) {
|
||||
console.log(` ✅ Found in production: ${prodDoc.title}`);
|
||||
console.log(` Category: ${prodDoc.category}`);
|
||||
console.log(` Order: ${prodDoc.order}`);
|
||||
console.log(` Sections: ${prodDoc.sections?.length || 0}`);
|
||||
console.log(` Visibility: ${prodDoc.visibility}`);
|
||||
} else {
|
||||
console.log(' ❌ NOT found in production');
|
||||
}
|
||||
|
||||
// 3. Check all research-theory documents in production
|
||||
console.log('\n3️⃣ All research-theory documents in production:\n');
|
||||
|
||||
const researchDocs = await prodClient.db(DB_NAME).collection('documents')
|
||||
.find({ category: 'research-theory' })
|
||||
.project({ slug: 1, title: 1, order: 1 })
|
||||
.sort({ order: 1 })
|
||||
.toArray();
|
||||
|
||||
researchDocs.forEach(doc => {
|
||||
console.log(` [${doc.order}] ${doc.slug}`);
|
||||
console.log(` ${doc.title}`);
|
||||
});
|
||||
|
||||
console.log(`\n Total: ${researchDocs.length} documents in research-theory`);
|
||||
|
||||
await prodClient.close();
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' DIAGNOSIS COMPLETE');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
91
scripts/export-research-and-import.js
Normal file
91
scripts/export-research-and-import.js
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Export tractatus-framework-research from dev and import to production
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
// 1. Export from dev
|
||||
const devClient = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient.connect();
|
||||
const devDoc = await devClient.db('tractatus_dev').collection('documents').findOne({
|
||||
slug: 'tractatus-framework-research'
|
||||
});
|
||||
await devClient.close();
|
||||
|
||||
if (!devDoc) {
|
||||
console.log('❌ Document not found in dev');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`✅ Exported from dev: ${devDoc.title}`);
|
||||
console.log(` Sections: ${devDoc.sections?.length}`);
|
||||
|
||||
// 2. Prep for production
|
||||
delete devDoc._id;
|
||||
devDoc.category = 'research-theory';
|
||||
devDoc.order = 2;
|
||||
devDoc.visibility = 'public';
|
||||
devDoc.updated_at = new Date();
|
||||
|
||||
// 3. Import to production
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
const prodClient = new MongoClient(MONGODB_URI);
|
||||
await prodClient.connect();
|
||||
|
||||
const existing = await prodClient.db(DB_NAME).collection('documents').findOne({
|
||||
slug: 'tractatus-framework-research'
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
console.log('⚠️ Already exists - replacing...');
|
||||
await prodClient.db(DB_NAME).collection('documents').replaceOne(
|
||||
{ slug: 'tractatus-framework-research' },
|
||||
devDoc
|
||||
);
|
||||
} else {
|
||||
console.log('📝 Inserting new...');
|
||||
await prodClient.db(DB_NAME).collection('documents').insertOne(devDoc);
|
||||
}
|
||||
|
||||
await prodClient.close();
|
||||
|
||||
console.log('✅ Imported to production');
|
||||
|
||||
// 4. Now run the organization fix on dev too
|
||||
const devClient2 = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient2.connect();
|
||||
|
||||
console.log('\n📝 Updating dev database...');
|
||||
|
||||
await devClient2.db('tractatus_dev').collection('documents').updateOne(
|
||||
{ slug: 'executive-summary-tractatus-inflection-point' },
|
||||
{
|
||||
$set: {
|
||||
category: 'research-theory',
|
||||
order: 1,
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
await devClient2.db('tractatus_dev').collection('documents').updateOne(
|
||||
{ slug: 'tractatus-framework-research' },
|
||||
{
|
||||
$set: {
|
||||
category: 'research-theory',
|
||||
order: 2,
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('✅ Dev database updated');
|
||||
await devClient2.close();
|
||||
|
||||
console.log('\n✅ COMPLETE - Both documents now in research-theory');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
40
scripts/export-research-doc.js
Normal file
40
scripts/export-research-doc.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* Export tractatus-framework-research document to JSON file
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
const fs = require('fs');
|
||||
|
||||
async function run() {
|
||||
const devClient = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient.connect();
|
||||
|
||||
const devDoc = await devClient.db('tractatus_dev').collection('documents').findOne({
|
||||
slug: 'tractatus-framework-research'
|
||||
});
|
||||
|
||||
await devClient.close();
|
||||
|
||||
if (!devDoc) {
|
||||
console.log('❌ Document not found in dev');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Prep for production
|
||||
delete devDoc._id;
|
||||
devDoc.category = 'research-theory';
|
||||
devDoc.order = 2;
|
||||
devDoc.visibility = 'public';
|
||||
devDoc.updated_at = new Date();
|
||||
|
||||
// Write to file
|
||||
fs.writeFileSync(
|
||||
'/tmp/tractatus-framework-research.json',
|
||||
JSON.stringify(devDoc, null, 2)
|
||||
);
|
||||
|
||||
console.log(`✅ Exported: ${devDoc.title}`);
|
||||
console.log(` Sections: ${devDoc.sections?.length || 0}`);
|
||||
console.log(` File: /tmp/tractatus-framework-research.json`);
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
108
scripts/fix-document-organization.js
Normal file
108
scripts/fix-document-organization.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
/**
|
||||
* Fix document organization issues:
|
||||
* 1. Move Executive Brief to research-theory
|
||||
* 2. Migrate tractatus-framework-research to production
|
||||
* 3. Ensure both are viewable as card documents
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
async function run() {
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db(DB_NAME);
|
||||
const collection = db.collection('documents');
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' FIXING DOCUMENT ORGANIZATION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// 1. Move Executive Brief from getting-started to research-theory
|
||||
console.log('1️⃣ Moving Executive Brief to Research & Theory...');
|
||||
|
||||
const execBriefResult = await collection.updateOne(
|
||||
{ slug: 'executive-summary-tractatus-inflection-point' },
|
||||
{
|
||||
$set: {
|
||||
category: 'research-theory',
|
||||
order: 1, // First in research section
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (execBriefResult.modifiedCount > 0) {
|
||||
console.log(' ✅ Executive Brief moved to research-theory (order: 1)');
|
||||
} else {
|
||||
console.log(' ⚠️ Executive Brief not found or already correct');
|
||||
}
|
||||
|
||||
// 2. Check if tractatus-framework-research exists in production
|
||||
console.log('\n2️⃣ Checking for tractatus-framework-research...');
|
||||
|
||||
const researchDoc = await collection.findOne({ slug: 'tractatus-framework-research' });
|
||||
|
||||
if (!researchDoc) {
|
||||
console.log(' ❌ Document missing from production - needs migration from dev');
|
||||
console.log(' 📝 Run migration script to copy from dev to prod');
|
||||
} else {
|
||||
console.log(' ✅ Document exists in production');
|
||||
console.log(` Category: ${researchDoc.category}`);
|
||||
console.log(` Order: ${researchDoc.order}`);
|
||||
console.log(` Sections: ${researchDoc.sections?.length || 0}`);
|
||||
|
||||
// Ensure it's in research-theory with correct order
|
||||
await collection.updateOne(
|
||||
{ slug: 'tractatus-framework-research' },
|
||||
{
|
||||
$set: {
|
||||
category: 'research-theory',
|
||||
order: 2, // Second in research section (after Executive Brief)
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
console.log(' ✅ Updated category and order');
|
||||
}
|
||||
|
||||
// 3. Renumber other research-theory documents
|
||||
console.log('\n3️⃣ Renumbering research-theory documents...');
|
||||
|
||||
const researchDocs = await collection.find({ category: 'research-theory' }).toArray();
|
||||
console.log(` Found ${researchDocs.length} research documents`);
|
||||
|
||||
const orderedSlugs = [
|
||||
'executive-summary-tractatus-inflection-point', // 1
|
||||
'tractatus-framework-research', // 2
|
||||
'pluralistic-values-research-foundations', // 3
|
||||
'the-27027-incident-a-case-study-in-pattern-recognition-bias', // 4
|
||||
'real-world-ai-governance-a-case-study-in-framework-failure-and-recovery', // 5
|
||||
'llm-integration-feasibility-research-scope', // 6
|
||||
'research-topic-concurrent-session-architecture', // 7
|
||||
'research-topic-rule-proliferation-transactional-overhead' // 8
|
||||
];
|
||||
|
||||
for (let i = 0; i < orderedSlugs.length; i++) {
|
||||
await collection.updateOne(
|
||||
{ slug: orderedSlugs[i] },
|
||||
{ $set: { order: i + 1, updated_at: new Date() } }
|
||||
);
|
||||
}
|
||||
|
||||
console.log(` ✅ Updated order for ${orderedSlugs.length} documents`);
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' SUMMARY');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
console.log('✅ Executive Brief moved to research-theory');
|
||||
console.log('✅ Document ordering updated');
|
||||
console.log('');
|
||||
|
||||
await client.close();
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
71
scripts/import-research-doc.js
Normal file
71
scripts/import-research-doc.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* Import tractatus-framework-research document from JSON file
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
const fs = require('fs');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' IMPORTING RESEARCH DOCUMENT TO PRODUCTION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// Read JSON file
|
||||
const doc = JSON.parse(fs.readFileSync('/tmp/tractatus-framework-research.json', 'utf8'));
|
||||
|
||||
console.log(`📄 Loaded: ${doc.title}`);
|
||||
console.log(` Slug: ${doc.slug}`);
|
||||
console.log(` Category: ${doc.category}`);
|
||||
console.log(` Order: ${doc.order}`);
|
||||
console.log(` Sections: ${doc.sections?.length || 0}\n`);
|
||||
|
||||
// Connect to production
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
console.log(`Database: ${DB_NAME}`);
|
||||
console.log(`URI: ${MONGODB_URI.replace(/:[^:]*@/, ':***@')}\n`);
|
||||
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
const db = client.db(DB_NAME);
|
||||
const collection = db.collection('documents');
|
||||
|
||||
// Check if exists
|
||||
const existing = await collection.findOne({ slug: doc.slug });
|
||||
|
||||
if (existing) {
|
||||
console.log('⚠️ Document already exists - replacing...\n');
|
||||
|
||||
const result = await collection.replaceOne(
|
||||
{ slug: doc.slug },
|
||||
doc
|
||||
);
|
||||
|
||||
console.log(`✅ Replaced: ${result.modifiedCount} document(s)`);
|
||||
} else {
|
||||
console.log('📝 Inserting new document...\n');
|
||||
|
||||
const result = await collection.insertOne(doc);
|
||||
|
||||
console.log(`✅ Inserted with ID: ${result.insertedId}`);
|
||||
}
|
||||
|
||||
// Verify
|
||||
const verification = await collection.findOne(
|
||||
{ slug: doc.slug },
|
||||
{ projection: { title: 1, category: 1, order: 1, visibility: 1 } }
|
||||
);
|
||||
|
||||
console.log('\n🔍 Verification:');
|
||||
console.log(JSON.stringify(verification, null, 2));
|
||||
|
||||
await client.close();
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' IMPORT COMPLETE');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
75
scripts/migrate-research-doc-to-prod.js
Normal file
75
scripts/migrate-research-doc-to-prod.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Migrate tractatus-framework-research from dev to production
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
|
||||
async function run() {
|
||||
// Connect to dev
|
||||
const devClient = new MongoClient('mongodb://localhost:27017');
|
||||
await devClient.connect();
|
||||
const devDb = devClient.db('tractatus_dev');
|
||||
|
||||
// Connect to prod
|
||||
const prodClient = new MongoClient(MONGODB_URI);
|
||||
await prodClient.connect();
|
||||
const prodDb = prodClient.db(process.env.MONGODB_DB || 'tractatus_prod');
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' MIGRATING tractatus-framework-research TO PRODUCTION');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
// Get document from dev
|
||||
const devDoc = await devDb.collection('documents').findOne({ slug: 'tractatus-framework-research' });
|
||||
|
||||
if (!devDoc) {
|
||||
console.log('❌ Document not found in dev database');
|
||||
await devClient.close();
|
||||
await prodClient.close();
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`📄 Found in dev: ${devDoc.title}`);
|
||||
console.log(` Sections: ${devDoc.sections?.length || 0}`);
|
||||
console.log(` Category: ${devDoc.category}`);
|
||||
|
||||
// Check if already exists in prod
|
||||
const prodDoc = await prodDb.collection('documents').findOne({ slug: 'tractatus-framework-research' });
|
||||
|
||||
if (prodDoc) {
|
||||
console.log('⚠️ Document already exists in production - updating instead...');
|
||||
|
||||
await prodDb.collection('documents').replaceOne(
|
||||
{ slug: 'tractatus-framework-research' },
|
||||
devDoc
|
||||
);
|
||||
|
||||
console.log('✅ Document updated in production');
|
||||
} else {
|
||||
console.log('📝 Inserting new document into production...');
|
||||
|
||||
// Remove _id to let MongoDB generate a new one
|
||||
delete devDoc._id;
|
||||
|
||||
// Ensure correct metadata
|
||||
devDoc.category = 'research-theory';
|
||||
devDoc.order = 2;
|
||||
devDoc.visibility = 'public';
|
||||
devDoc.updated_at = new Date();
|
||||
|
||||
await prodDb.collection('documents').insertOne(devDoc);
|
||||
|
||||
console.log('✅ Document inserted into production');
|
||||
}
|
||||
|
||||
await devClient.close();
|
||||
await prodClient.close();
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' MIGRATION COMPLETE');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
102
scripts/reorganize-document-categories.js
Normal file
102
scripts/reorganize-document-categories.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
/**
|
||||
* Reorganize document categories as requested:
|
||||
* 1. Move implementation guides from getting-started to resources category
|
||||
* 2. Move Glossary from technical-reference to getting-started
|
||||
* 3. Ensure Research & Theory section is collapsed by default
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' REORGANIZING DOCUMENT CATEGORIES');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
const db = client.db(DB_NAME);
|
||||
const collection = db.collection('documents');
|
||||
|
||||
// 1. Find guides in Getting Started
|
||||
console.log('1️⃣ Finding implementation guides in Getting Started...\n');
|
||||
|
||||
const guides = await collection.find({
|
||||
category: 'getting-started',
|
||||
slug: { $in: [
|
||||
'implementation-guide-v1.1',
|
||||
'implementation-guide',
|
||||
'implementation-guide-python-examples'
|
||||
]}
|
||||
}).project({ slug: 1, title: 1 }).toArray();
|
||||
|
||||
console.log(`Found ${guides.length} guides:`);
|
||||
guides.forEach(g => console.log(` - ${g.slug}`));
|
||||
|
||||
// 2. Move guides to resources category
|
||||
if (guides.length > 0) {
|
||||
console.log('\n📝 Moving guides to resources category...\n');
|
||||
|
||||
const result = await collection.updateMany(
|
||||
{ slug: { $in: guides.map(g => g.slug) } },
|
||||
{
|
||||
$set: {
|
||||
category: 'resources',
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`✅ Moved ${result.modifiedCount} guides to resources`);
|
||||
}
|
||||
|
||||
// 3. Move Glossary to getting-started
|
||||
console.log('\n2️⃣ Moving Glossary to Getting Started...\n');
|
||||
|
||||
const glossaryResult = await collection.updateOne(
|
||||
{ slug: 'GLOSSARY' },
|
||||
{
|
||||
$set: {
|
||||
category: 'getting-started',
|
||||
order: 10,
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (glossaryResult.modifiedCount > 0) {
|
||||
console.log('✅ Glossary moved to getting-started (order: 10)');
|
||||
} else {
|
||||
console.log('⚠️ Glossary not found or already in getting-started');
|
||||
}
|
||||
|
||||
// 4. Verify final state
|
||||
console.log('\n3️⃣ Verifying final categories...\n');
|
||||
|
||||
const gettingStarted = await collection.find({ category: 'getting-started', visibility: 'public' })
|
||||
.project({ slug: 1, title: 1, order: 1 })
|
||||
.sort({ order: 1 })
|
||||
.toArray();
|
||||
|
||||
console.log(`Getting Started (${gettingStarted.length} documents):`);
|
||||
gettingStarted.forEach(d => console.log(` [${d.order}] ${d.slug}`));
|
||||
|
||||
const resources = await collection.find({ category: 'resources', visibility: 'public' })
|
||||
.project({ slug: 1, title: 1, order: 1 })
|
||||
.sort({ order: 1 })
|
||||
.toArray();
|
||||
|
||||
console.log(`\nResources (${resources.length} documents):`);
|
||||
resources.forEach(d => console.log(` [${d.order || 'none'}] ${d.slug}`));
|
||||
|
||||
await client.close();
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' REORGANIZATION COMPLETE');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
92
scripts/unarchive-glossary.js
Normal file
92
scripts/unarchive-glossary.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/**
|
||||
* Unarchive and publish the best Glossary document
|
||||
*/
|
||||
const { MongoClient } = require('mongodb');
|
||||
require('dotenv').config({ path: '/var/www/tractatus/.env' });
|
||||
|
||||
async function run() {
|
||||
const MONGODB_URI = process.env.MONGODB_URI || 'mongodb://localhost:27017';
|
||||
const DB_NAME = process.env.MONGODB_DB || 'tractatus_prod';
|
||||
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
await client.connect();
|
||||
|
||||
console.log('═══════════════════════════════════════════════════════════');
|
||||
console.log(' UNARCHIVING GLOSSARY');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
|
||||
const db = client.db(DB_NAME);
|
||||
const collection = db.collection('documents');
|
||||
|
||||
// Find all glossary documents
|
||||
const glossaries = await collection.find({
|
||||
slug: { $regex: 'glossary', $options: 'i' }
|
||||
}).toArray();
|
||||
|
||||
console.log(`Found ${glossaries.length} glossary document(s)\n`);
|
||||
|
||||
// Pick the one with most sections
|
||||
let best = null;
|
||||
let bestSections = 0;
|
||||
|
||||
glossaries.forEach((doc, idx) => {
|
||||
const sections = doc.sections?.length || 0;
|
||||
console.log(`${idx + 1}. ${doc.slug}`);
|
||||
console.log(` Title: ${doc.title}`);
|
||||
console.log(` Sections: ${sections}`);
|
||||
console.log(` Visibility: ${doc.visibility}`);
|
||||
console.log(` Category: ${doc.category || 'none'}`);
|
||||
console.log('');
|
||||
|
||||
if (sections > bestSections) {
|
||||
best = doc;
|
||||
bestSections = sections;
|
||||
}
|
||||
});
|
||||
|
||||
if (!best) {
|
||||
console.log('❌ No glossary documents found');
|
||||
await client.close();
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ Best version: ${best.slug} (${bestSections} sections)\n`);
|
||||
|
||||
// Unarchive it
|
||||
if (best.visibility !== 'public') {
|
||||
console.log('📝 Updating to public...\n');
|
||||
|
||||
const result = await collection.updateOne(
|
||||
{ _id: best._id },
|
||||
{
|
||||
$set: {
|
||||
visibility: 'public',
|
||||
category: 'technical-reference',
|
||||
order: 100, // Place at end of technical reference
|
||||
updated_at: new Date()
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`✅ Updated: ${result.modifiedCount} document(s)`);
|
||||
|
||||
// Verify
|
||||
const updated = await collection.findOne(
|
||||
{ _id: best._id },
|
||||
{ projection: { slug: 1, title: 1, visibility: 1, category: 1, order: 1 } }
|
||||
);
|
||||
|
||||
console.log('\n🔍 Verification:');
|
||||
console.log(JSON.stringify(updated, null, 2));
|
||||
} else {
|
||||
console.log('✅ Already public - no update needed');
|
||||
}
|
||||
|
||||
await client.close();
|
||||
|
||||
console.log('\n═══════════════════════════════════════════════════════════');
|
||||
console.log(' COMPLETE');
|
||||
console.log('═══════════════════════════════════════════════════════════\n');
|
||||
}
|
||||
|
||||
run().catch(console.error);
|
||||
|
|
@ -20,8 +20,10 @@ router.get('/search',
|
|||
asyncHandler(documentsController.searchDocuments)
|
||||
);
|
||||
|
||||
// GET /api/documents/archived
|
||||
// GET /api/documents/archived (admin only)
|
||||
router.get('/archived',
|
||||
authenticateToken,
|
||||
requireRole('admin'),
|
||||
asyncHandler(documentsController.listArchivedDocuments)
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue