#!/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)
})