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:
TheFlow 2025-10-26 00:03:13 +13:00
parent 043d14a499
commit be53ab36f8
14 changed files with 826 additions and 91 deletions

View file

@ -534,49 +534,6 @@
<div class="text-sm text-gray-500">Loading...</div> <div class="text-sm text-gray-500">Loading...</div>
</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 --> <!-- GitHub Section -->
<div class="mt-6 pt-6 border-t border-gray-200"> <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"> <h3 class="font-semibold text-gray-900 mb-3 flex items-center gap-2">

View file

@ -12,7 +12,7 @@ const CATEGORIES = {
'getting-started': { 'getting-started': {
label: '📚 Getting Started', label: '📚 Getting Started',
icon: '📚', icon: '📚',
description: 'Introduction, core concepts, and implementation guides', description: 'Introduction, core concepts, and glossary',
order: 1, order: 1,
color: 'blue', color: 'blue',
bgColor: 'bg-blue-50', bgColor: 'bg-blue-50',
@ -20,22 +20,33 @@ const CATEGORIES = {
textColor: 'text-blue-700', textColor: 'text-blue-700',
collapsed: false 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': { 'research-theory': {
label: '🔬 Research & Theory', label: '🔬 Research & Theory',
icon: '🔬', icon: '🔬',
description: 'Research papers, case studies, theoretical foundations', description: 'Research papers, case studies, theoretical foundations',
order: 2, order: 3,
color: 'purple', color: 'purple',
bgColor: 'bg-purple-50', bgColor: 'bg-purple-50',
borderColor: 'border-l-4 border-purple-500', borderColor: 'border-l-4 border-purple-500',
textColor: 'text-purple-700', textColor: 'text-purple-700',
collapsed: false // Expanded to show Working Paper v0.1 collapsed: true
}, },
'technical-reference': { 'technical-reference': {
label: '🔌 Technical Reference', label: '🔌 Technical Reference',
icon: '🔌', icon: '🔌',
description: 'API documentation, code examples, architecture', description: 'API documentation, code examples, architecture',
order: 3, order: 4,
color: 'green', color: 'green',
bgColor: 'bg-green-50', bgColor: 'bg-green-50',
borderColor: 'border-l-4 border-green-500', borderColor: 'border-l-4 border-green-500',
@ -46,7 +57,7 @@ const CATEGORIES = {
label: '🎓 Advanced Topics', label: '🎓 Advanced Topics',
icon: '🎓', icon: '🎓',
description: 'Value pluralism, organizational theory, advanced concepts', description: 'Value pluralism, organizational theory, advanced concepts',
order: 4, order: 5,
color: 'teal', color: 'teal',
bgColor: 'bg-teal-50', bgColor: 'bg-teal-50',
borderColor: 'border-l-4 border-teal-500', borderColor: 'border-l-4 border-teal-500',
@ -57,7 +68,7 @@ const CATEGORIES = {
label: '💼 Business & Leadership', label: '💼 Business & Leadership',
icon: '💼', icon: '💼',
description: 'Business cases, ROI analysis, executive briefs', description: 'Business cases, ROI analysis, executive briefs',
order: 5, order: 6,
color: 'pink', color: 'pink',
bgColor: 'bg-pink-50', bgColor: 'bg-pink-50',
borderColor: 'border-l-4 border-pink-500', borderColor: 'border-l-4 border-pink-500',
@ -172,13 +183,8 @@ async function loadDocuments() {
const data = await response.json(); const data = await response.json();
documents = data.documents || []; 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'); 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>'; listEl.innerHTML = '<div class="text-sm text-gray-500">No documents available</div>';
return; 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; listEl.innerHTML = html;
// Apply collapsed state to categories (CSP-compliant - no inline styles) // Apply collapsed state to categories (CSP-compliant - no inline styles)

View file

@ -5,7 +5,7 @@
* - PWA functionality * - PWA functionality
*/ */
const CACHE_VERSION = '0.1.1'; const CACHE_VERSION = '0.1.4';
const CACHE_NAME = `tractatus-v${CACHE_VERSION}`; const CACHE_NAME = `tractatus-v${CACHE_VERSION}`;
const VERSION_CHECK_INTERVAL = 3600000; // 1 hour in milliseconds const VERSION_CHECK_INTERVAL = 3600000; // 1 hour in milliseconds

48
scripts/check-glossary.js Normal file
View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View 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);

View file

@ -20,8 +20,10 @@ router.get('/search',
asyncHandler(documentsController.searchDocuments) asyncHandler(documentsController.searchDocuments)
); );
// GET /api/documents/archived // GET /api/documents/archived (admin only)
router.get('/archived', router.get('/archived',
authenticateToken,
requireRole('admin'),
asyncHandler(documentsController.listArchivedDocuments) asyncHandler(documentsController.listArchivedDocuments)
); );