tractatus/scripts/migrate-public-to-visibility.js
TheFlow f8ef2128fc refactor(data): migrate legacy public field to modern visibility field
SUMMARY:
Completed migration from deprecated 'public: true/false' field to modern
'visibility' field across entire codebase. Ensures single source of truth
for document visibility state.

MIGRATION EXECUTION:
✓ Created migration script with dry-run support
✓ Migrated 120 documents in database (removed deprecated field)
✓ Post-migration: 0 documents with 'public' field, 127 with 'visibility'
✓ Zero data loss - all documents already had visibility set correctly

CODE CHANGES:

1. Database Migration (scripts/migrate-public-to-visibility.js):
   - Created safe migration with dry-run mode
   - Handles documents with both fields (cleanup)
   - Post-migration verification built-in
   - Execution: node scripts/migrate-public-to-visibility.js --execute

2. Document Model (src/models/Document.model.js):
   - Removed 'public' field from create() method
   - Updated findByQuadrant() to use visibility: 'public'
   - Updated findByAudience() to use visibility: 'public'
   - Updated search() to use visibility: 'public'

3. API Controller (src/controllers/documents.controller.js):
   - Removed legacy filter: { public: true, visibility: { $exists: false } }
   - listDocuments() now uses clean filter: visibility: 'public'
   - searchDocuments() now uses clean filter: visibility: 'public'

4. Scripts Updated:
   - upload-document.js: Removed public: true
   - seed-architectural-safeguards-document.js: Removed public: true
   - import-5-archives.js: Removed public: true
   - verify-34-documents.js: Updated query filter to use visibility
   - query-all-documents.js: Updated query filter to use visibility

VERIFICATION:
✓ 0 remaining 'public: true/false' usages in src/ and scripts/
✓ All documents use visibility field exclusively
✓ API queries now filter on visibility only
✓ Backward compatibility code removed

DATA MODEL:
Before: { public: true, visibility: 'public' } (redundant)
After:  { visibility: 'public' } (single source of truth)

BENEFITS:
- Cleaner data model
- Single source of truth for visibility
- Simplified API logic
- Removed backward compatibility overhead
- Consistent with document security model

FRAMEWORK COMPLIANCE:
Addresses SCHEDULED_TASKS.md item "Legacy public Field Migration"
Completes Sprint 2 Medium Priority task

NEXT STEPS (Optional):
- Deploy migration to production
- Monitor for any edge cases
- Consider adding visibility to database indexes

🤖 Generated with Claude Code (https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-19 13:49:21 +13:00

138 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* Migrate legacy 'public' field to modern 'visibility' field
*
* SECURITY: Safe migration with dry-run support
* - Migrates public: true → visibility: 'public'
* - Migrates public: false → visibility: 'internal'
* - Preserves documents that already have visibility set
* - Removes the deprecated 'public' field after migration
*/
const { getCollection } = require('../src/utils/db.util');
async function migrate(dryRun = false) {
try {
const collection = await getCollection('documents');
// Find documents with public field but no visibility
const docsWithPublicOnly = await collection.find({
public: { $exists: true },
visibility: { $exists: false }
}).toArray();
// Find documents with both fields (inconsistent state)
const docsWithBoth = await collection.find({
public: { $exists: true },
visibility: { $exists: true }
}).toArray();
console.log('\n📊 Migration Analysis:');
console.log(` Documents with only 'public' field: ${docsWithPublicOnly.length}`);
console.log(` Documents with both fields: ${docsWithBoth.length}`);
console.log(` Total to migrate: ${docsWithPublicOnly.length + docsWithBoth.length}`);
if (docsWithPublicOnly.length === 0 && docsWithBoth.length === 0) {
console.log('\n✅ No documents need migration. All documents already use visibility field.');
return;
}
if (dryRun) {
console.log('\n🔍 DRY RUN - No changes will be made\n');
// Show what would be migrated
if (docsWithPublicOnly.length > 0) {
console.log('Documents with only public field:');
docsWithPublicOnly.forEach(doc => {
const newVisibility = doc.public ? 'public' : 'internal';
console.log(` - ${doc.title} (${doc.slug})`);
console.log(` public: ${doc.public} → visibility: '${newVisibility}'`);
});
}
if (docsWithBoth.length > 0) {
console.log('\nDocuments with both fields (will remove public):');
docsWithBoth.forEach(doc => {
console.log(` - ${doc.title} (${doc.slug})`);
console.log(` current: public=${doc.public}, visibility='${doc.visibility}'`);
console.log(` action: Keep visibility='${doc.visibility}', remove public field`);
});
}
console.log('\n💡 Run with --execute to perform migration');
return;
}
// Perform actual migration
console.log('\n🔄 Performing migration...\n');
let migratedCount = 0;
// Migrate documents with only public field
for (const doc of docsWithPublicOnly) {
const visibility = doc.public ? 'public' : 'internal';
await collection.updateOne(
{ _id: doc._id },
{
$set: { visibility },
$unset: { public: "" }
}
);
console.log(`${doc.title}: public=${doc.public} → visibility='${visibility}'`);
migratedCount++;
}
// Clean up documents with both fields (keep visibility, remove public)
for (const doc of docsWithBoth) {
await collection.updateOne(
{ _id: doc._id },
{ $unset: { public: "" } }
);
console.log(`${doc.title}: Removed public field, kept visibility='${doc.visibility}'`);
migratedCount++;
}
console.log(`\n✅ Migration complete! ${migratedCount} documents updated.`);
// Verify results
const remainingWithPublic = await collection.countDocuments({ public: { $exists: true } });
const totalWithVisibility = await collection.countDocuments({ visibility: { $exists: true } });
console.log('\n📊 Post-migration verification:');
console.log(` Documents with 'public' field: ${remainingWithPublic}`);
console.log(` Documents with 'visibility' field: ${totalWithVisibility}`);
if (remainingWithPublic > 0) {
console.warn('\n⚠ Warning: Some documents still have the public field. Review manually.');
} else {
console.log('\n✅ All documents successfully migrated to visibility field!');
}
} catch (error) {
console.error('❌ Migration failed:', error);
throw error;
}
}
// CLI interface
const args = process.argv.slice(2);
const dryRun = !args.includes('--execute');
if (dryRun) {
console.log('🔍 Running in DRY RUN mode (no changes will be made)');
console.log(' Use --execute flag to perform actual migration\n');
}
migrate(dryRun)
.then(() => {
console.log('\n✨ Script complete');
process.exit(0);
})
.catch((error) => {
console.error('\n💥 Script failed:', error);
process.exit(1);
});