Files
telegram-management-system/marketing-agent/scripts/update-models-tenant.js
你的用户名 237c7802e5
Some checks failed
Deploy / deploy (push) Has been cancelled
Initial commit: Telegram Management System
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>
2025-11-04 15:37:50 +08:00

189 lines
6.0 KiB
JavaScript
Executable File
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
/**
* 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();