Initial commit: Telegram Management System
Some checks failed
Deploy / deploy (push) Has been cancelled
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>
This commit is contained in:
189
marketing-agent/scripts/update-models-tenant.js
Executable file
189
marketing-agent/scripts/update-models-tenant.js
Executable file
@@ -0,0 +1,189 @@
|
||||
#!/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();
|
||||
Reference in New Issue
Block a user