OverviewFilesCommitsIssuesDocsReleasesPeers

wirecore / scripts / migrate-user-directories.mjs

147 lines · javascript ref:
#!/usr/bin/env node

/**
 * Migrate user directories to flatten nested structure caused by '/' in public keys
 *
 * Problem: Base64 public keys contain '/', causing nested directories:
 *   data/users/wirecore-web-users/wbaroq+lVTy/BP1Q/
 *
 * Solution: Flatten to:
 *   data/users/wirecore-web-users/wbaroq+lVTy_BP1Q/
 */

import { readdir, rename, stat } from 'fs/promises'
import { join } from 'path'
import { existsSync } from 'fs'

const USER_DIR = './data/users/wirecore-web-users'

async function findNestedDirectories(baseDir) {
  const nested = []

  try {
    const entries = await readdir(baseDir)

    for (const entry of entries) {
      const entryPath = join(baseDir, entry)
      const stats = await stat(entryPath)

      if (stats.isDirectory()) {
        // Check if this directory contains another directory (nested)
        const subEntries = await readdir(entryPath)
        const subDirs = []

        for (const subEntry of subEntries) {
          const subPath = join(entryPath, subEntry)
          const subStats = await stat(subPath)
          if (subStats.isDirectory()) {
            // Check if this looks like a user storage directory (has feeds/ and metadata.json)
            const hasFeedsDir = existsSync(join(subPath, 'feeds'))
            const hasMetadata = existsSync(join(subPath, 'metadata.json'))

            if (hasFeedsDir && hasMetadata) {
              subDirs.push({
                parent: entry,
                child: subEntry,
                fullPath: subPath
              })
            }
          }
        }

        if (subDirs.length > 0) {
          nested.push(...subDirs)
        }
      }
    }
  } catch (err) {
    console.error('Error scanning directories:', err)
  }

  return nested
}

async function flattenDirectory(nested) {
  const { parent, child, fullPath } = nested

  // Create new flat name by replacing '/' with '_'
  const flatName = `${parent}_${child}`
  const newPath = join(USER_DIR, flatName)

  console.log(`Flattening: ${parent}/${child} -> ${flatName}`)

  try {
    // Move the nested directory to the flat location
    await rename(fullPath, newPath)
    console.log(`✓ Moved to ${flatName}`)

    // Try to remove the now-empty parent directory
    try {
      const parentPath = join(USER_DIR, parent)
      const remaining = await readdir(parentPath)
      if (remaining.length === 0) {
        await rename(parentPath, parentPath + '.old') // Rename instead of delete for safety
        console.log(`  Marked old parent directory: ${parent}.old`)
      }
    } catch (err) {
      console.log(`  Note: Parent directory not empty or couldn't be removed`)
    }

    return true
  } catch (err) {
    console.error(`✗ Failed to flatten ${parent}/${child}:`, err.message)
    return false
  }
}

async function main() {
  console.log('🔧 Migrating user directories...')
  console.log(`Directory: ${USER_DIR}\n`)

  if (!existsSync(USER_DIR)) {
    console.log('❌ User directory does not exist')
    return
  }

  // Find nested directories
  const nested = await findNestedDirectories(USER_DIR)

  if (nested.length === 0) {
    console.log('✅ No nested directories found. Already migrated!')
    return
  }

  console.log(`Found ${nested.length} nested director${nested.length === 1 ? 'y' : 'ies'}:\n`)
  nested.forEach(n => console.log(`  - ${n.parent}/${n.child}`))
  console.log()

  // Flatten each nested directory
  let success = 0
  let failed = 0

  for (const n of nested) {
    const result = await flattenDirectory(n)
    if (result) {
      success++
    } else {
      failed++
    }
  }

  console.log()
  console.log('📊 Migration complete:')
  console.log(`  ✓ Success: ${success}`)
  console.log(`  ✗ Failed: ${failed}`)

  if (failed === 0) {
    console.log('\n✅ All directories migrated successfully!')
    console.log('   Old parent directories marked with .old suffix')
    console.log('   You can delete them after verifying the migration')
  }
}

main().catch(err => {
  console.error('Migration error:', err)
  process.exit(1)
})