Some checks failed
Deploy / deploy (push) Has been cancelled
Full-stack web application for Telegram management - Frontend: Vue 3 + Vben Admin - Backend: NestJS - Features: User management, group broadcast, statistics 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
189 lines
6.0 KiB
JavaScript
Executable File
189 lines
6.0 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Script to update all MongoDB models to include tenantId field
|
||
* This script adds tenant isolation to all existing models
|
||
*/
|
||
|
||
import { promises as fs } from 'fs';
|
||
import path from 'path';
|
||
import { fileURLToPath } from 'url';
|
||
|
||
const __filename = fileURLToPath(import.meta.url);
|
||
const __dirname = path.dirname(__filename);
|
||
|
||
// Models that should NOT have tenantId (system-wide models)
|
||
const EXCLUDED_MODELS = [
|
||
'Tenant.js',
|
||
'Language.js', // System languages are shared
|
||
];
|
||
|
||
// Models that need special handling
|
||
const SPECIAL_MODELS = {
|
||
'User.js': 'Already updated with tenantId',
|
||
'Role.js': 'System roles are shared across tenants',
|
||
};
|
||
|
||
async function updateModel(filePath) {
|
||
const fileName = path.basename(filePath);
|
||
|
||
// Skip excluded models
|
||
if (EXCLUDED_MODELS.includes(fileName)) {
|
||
console.log(`⏩ Skipping ${fileName} (excluded)`);
|
||
return;
|
||
}
|
||
|
||
// Skip special models
|
||
if (SPECIAL_MODELS[fileName]) {
|
||
console.log(`⏩ Skipping ${fileName} (${SPECIAL_MODELS[fileName]})`);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
let content = await fs.readFile(filePath, 'utf8');
|
||
|
||
// Check if model already has tenantId
|
||
if (content.includes('tenantId:')) {
|
||
console.log(`✅ ${fileName} already has tenantId`);
|
||
return;
|
||
}
|
||
|
||
// Find the schema definition
|
||
const schemaPattern = /const\s+\w+Schema\s*=\s*new\s+(?:mongoose\.)?Schema\s*\(\s*\{/;
|
||
const match = content.match(schemaPattern);
|
||
|
||
if (!match) {
|
||
console.log(`⚠️ ${fileName} - Could not find schema definition`);
|
||
return;
|
||
}
|
||
|
||
// Insert tenantId field after schema opening
|
||
const insertPosition = match.index + match[0].length;
|
||
const tenantIdField = `
|
||
// Multi-tenant support
|
||
tenantId: {
|
||
type: mongoose.Schema.Types.ObjectId,
|
||
ref: 'Tenant',
|
||
required: true,
|
||
index: true
|
||
},`;
|
||
|
||
content = content.slice(0, insertPosition) + tenantIdField + content.slice(insertPosition);
|
||
|
||
// Update indexes to include tenantId
|
||
// Find existing index definitions
|
||
const indexPattern = /(\w+Schema\.index\s*\(\s*\{[^}]+\}\s*(?:,\s*\{[^}]+\})?\s*\);?)/g;
|
||
let indexMatches = [...content.matchAll(indexPattern)];
|
||
|
||
if (indexMatches.length > 0) {
|
||
// Add compound indexes with tenantId
|
||
let additionalIndexes = '\n\n// Multi-tenant indexes';
|
||
|
||
indexMatches.forEach(match => {
|
||
const indexDef = match[1];
|
||
// Extract the index fields
|
||
const fieldsMatch = indexDef.match(/\{([^}]+)\}/);
|
||
if (fieldsMatch) {
|
||
const fields = fieldsMatch[1].trim();
|
||
// Skip if already includes tenantId
|
||
if (!fields.includes('tenantId')) {
|
||
// Create compound index with tenantId
|
||
const newIndex = indexDef.replace(/\{([^}]+)\}/, '{ tenantId: 1, $1 }');
|
||
additionalIndexes += '\n' + newIndex;
|
||
}
|
||
}
|
||
});
|
||
|
||
// Find where to insert the new indexes (after existing indexes)
|
||
const lastIndexMatch = indexMatches[indexMatches.length - 1];
|
||
const insertPos = lastIndexMatch.index + lastIndexMatch[0].length;
|
||
content = content.slice(0, insertPos) + additionalIndexes + content.slice(insertPos);
|
||
} else {
|
||
// No existing indexes, add basic tenantId index after schema definition
|
||
const schemaEndPattern = /}\s*(?:,\s*\{[^}]+\})?\s*\);/;
|
||
const schemaEndMatch = content.match(schemaEndPattern);
|
||
if (schemaEndMatch) {
|
||
const insertPos = schemaEndMatch.index + schemaEndMatch[0].length;
|
||
const basicIndex = '\n\n// Multi-tenant index\n' +
|
||
fileName.replace('.js', '') + 'Schema.index({ tenantId: 1 });';
|
||
content = content.slice(0, insertPos) + basicIndex + content.slice(insertPos);
|
||
}
|
||
}
|
||
|
||
// Update unique indexes to be unique within tenant
|
||
content = content.replace(
|
||
/index\s*\(\s*\{([^}]+)\}\s*,\s*\{\s*unique:\s*true\s*\}\s*\)/g,
|
||
(match, fields) => {
|
||
if (!fields.includes('tenantId')) {
|
||
return `index({ tenantId: 1, ${fields} }, { unique: true })`;
|
||
}
|
||
return match;
|
||
}
|
||
);
|
||
|
||
// Save the updated file
|
||
await fs.writeFile(filePath, content, 'utf8');
|
||
console.log(`✨ Updated ${fileName} with tenantId support`);
|
||
|
||
} catch (error) {
|
||
console.error(`❌ Error updating ${filePath}:`, error.message);
|
||
}
|
||
}
|
||
|
||
async function findModels(dir) {
|
||
const models = [];
|
||
|
||
async function walk(currentDir) {
|
||
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
||
|
||
for (const entry of entries) {
|
||
const fullPath = path.join(currentDir, entry.name);
|
||
|
||
if (entry.isDirectory()) {
|
||
// Skip node_modules and other non-service directories
|
||
if (!entry.name.includes('node_modules') &&
|
||
!entry.name.startsWith('.') &&
|
||
entry.name !== 'scripts' &&
|
||
entry.name !== 'frontend') {
|
||
await walk(fullPath);
|
||
}
|
||
} else if (entry.isFile() && entry.name.endsWith('.js')) {
|
||
// Check if it's in a models directory
|
||
if (currentDir.includes('/models')) {
|
||
models.push(fullPath);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
await walk(dir);
|
||
return models;
|
||
}
|
||
|
||
async function main() {
|
||
try {
|
||
console.log('🔍 Finding all model files...');
|
||
const projectRoot = path.join(__dirname, '..');
|
||
const models = await findModels(path.join(projectRoot, 'services'));
|
||
|
||
console.log(`\n📋 Found ${models.length} model files\n`);
|
||
|
||
for (const model of models) {
|
||
await updateModel(model);
|
||
}
|
||
|
||
console.log('\n✅ Model update complete!');
|
||
console.log('\n⚠️ Important next steps:');
|
||
console.log('1. Review the changes to ensure they are correct');
|
||
console.log('2. Update all queries to include tenantId filtering');
|
||
console.log('3. Update all create operations to include tenantId');
|
||
console.log('4. Test thoroughly to ensure tenant isolation works');
|
||
|
||
} catch (error) {
|
||
console.error('❌ Script failed:', error);
|
||
process.exit(1);
|
||
}
|
||
}
|
||
|
||
// Run the script
|
||
main(); |