Initial commit: Telegram Management System
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:
你的用户名
2025-11-04 15:37:50 +08:00
commit 237c7802e5
3674 changed files with 525172 additions and 0 deletions

View 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();