@brandazm/dynamic-permissions
Version:
A flexible and powerful permissions management system for NestJS applications with built-in security features
272 lines (268 loc) • 12.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationGeneratorService = void 0;
const common_1 = require("@nestjs/common");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const migration_exception_1 = require("../exceptions/migration.exception");
let MigrationGeneratorService = class MigrationGeneratorService {
async generateMigration(name, directory = 'src/migrations') {
try {
console.log('Creating migration directory:', directory);
if (!fs.existsSync(directory)) {
fs.mkdirSync(directory, { recursive: true });
}
console.log('Loading configuration...');
const config = await this.loadConfig();
console.log('Configuration loaded successfully');
console.log('Generating migration content...');
const migrationContent = this.generateMigrationContent(config, name);
console.log('Migration content generated successfully');
const timestamp = new Date().getTime();
const className = this.generateClassName(name);
const filename = `${timestamp}-${className}.ts`;
const filePath = path.join(directory, filename);
console.log('Writing migration file to:', filePath);
fs.writeFileSync(filePath, migrationContent, 'utf8');
console.log(`Migration generated successfully at: ${filePath}`);
}
catch (error) {
console.error('Error generating migration:', error);
throw new migration_exception_1.MigrationException('MIGRATION_FAILED', error.message);
}
}
async loadConfig() {
const tsConfigPath = path.join(process.cwd(), 'config', 'permissions.config.ts');
try {
const configFileContent = fs.readFileSync(tsConfigPath, 'utf8');
const [, fileContent] = configFileContent.split('=');
return JSON.parse(fileContent);
}
catch (error) {
console.error('Error loading configuration file:', error);
throw new migration_exception_1.MigrationException('CONFIG_LOAD_ERROR', `Failed to load config: ${error.message}`);
}
}
generateClassName(name) {
return (name
.split(/[-_]/)
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
.join('') + `_${Date.now()}`);
}
generateMigrationContent(config, name) {
const createTables = this.generateCreateTables(config);
const indexes = this.generateIndexes(config);
return `import { MigrationInterface, QueryRunner } from 'typeorm';
export class ${this.generateClassName(name)} implements MigrationInterface {
name = '${Date.now()}';
public async up(queryRunner: QueryRunner): Promise<void> {
${createTables
.split('\n\n')
.map((table) => `await queryRunner.query(\`\n${table}\n \`);`)
.join('\n\n ')}
${indexes.map((index) => `await queryRunner.query(\`${index}\`);`).join('\n ')}
}
public async down(queryRunner: QueryRunner): Promise<void> {
// Drop tables in reverse order
await queryRunner.query(\`DROP TABLE IF EXISTS "${config.database.entities.userPermissions.tableName}"\`);
await queryRunner.query(\`DROP TABLE IF EXISTS "${config.database.entities.routerPermissions.tableName}"\`);
await queryRunner.query(\`DROP TABLE IF EXISTS "${config.database.entities.permissions.tableName}"\`);
}
}`;
}
generateCreateTables(config) {
const { database } = config;
const permissionFields = {};
const routerPermissionFields = {};
const userPermissionFields = {};
Object.entries(database.entities.permissions.fields).forEach(([key, value]) => {
if (value)
permissionFields[key] = value;
});
Object.entries(database.entities.routerPermissions.fields).forEach(([key, value]) => {
if (value)
routerPermissionFields[key] = value;
});
Object.entries(database.entities.userPermissions.fields).forEach(([key, value]) => {
if (value)
userPermissionFields[key] = value;
});
const tables = [
this.generateTableCreation(database.entities.permissions.tableName, permissionFields),
this.generateTableCreation(database.entities.routerPermissions.tableName, routerPermissionFields),
this.generateTableCreation(database.entities.userPermissions.tableName, userPermissionFields),
];
return tables.join('\n\n');
}
generateAlterTables(oldConfig, newConfig) {
const alterations = [];
const convertToEntityFields = (config) => {
const result = {};
Object.entries(config.database.entities).forEach(([key, entity]) => {
const fields = {};
Object.entries(entity.fields).forEach(([fieldKey, value]) => {
if (value)
fields[fieldKey] = value;
});
result[key] = {
tableName: entity.tableName,
fields,
};
});
return result;
};
const oldEntities = convertToEntityFields(oldConfig);
const newEntities = convertToEntityFields(newConfig);
Object.keys(oldEntities).forEach((key) => {
this.compareAndGenerateAlterations(oldEntities[key], newEntities[key], alterations);
});
return alterations;
}
generateTableCreation(tableName, fields) {
const columns = Object.entries(fields)
.map(([key, value]) => {
let type = 'varchar';
let constraints = '';
if (key === 'id') {
type = 'uuid';
constraints = 'PRIMARY KEY DEFAULT gen_random_uuid()';
}
else if (key.includes('At')) {
type = 'timestamp';
constraints = 'NOT NULL DEFAULT CURRENT_TIMESTAMP';
}
else if (key === 'isActive' || key.includes('is_active')) {
type = 'boolean';
constraints = 'NOT NULL DEFAULT true';
}
else if (key === 'level') {
type = 'integer';
constraints = 'NOT NULL DEFAULT 0';
}
else if (key === 'name') {
type = 'varchar';
constraints = 'NOT NULL';
}
else {
type = 'varchar';
}
return ` "${value}" ${type}${constraints ? ' ' + constraints : ''}`;
})
.join(',\n');
return `CREATE TABLE "${tableName}" (\n${columns}\n )`;
}
compareAndGenerateAlterations(oldEntity, newEntity, alterations) {
if (oldEntity.tableName !== newEntity.tableName) {
alterations.push(`ALTER TABLE "${oldEntity.tableName}" RENAME TO "${newEntity.tableName}";`);
}
const oldFields = new Set(Object.values(oldEntity.fields));
const newFields = new Set(Object.values(newEntity.fields));
for (const [key, value] of Object.entries(newEntity.fields)) {
if (!oldFields.has(value)) {
let type = 'varchar';
if (key === 'id')
type = 'uuid';
if (key.includes('At'))
type = 'timestamp';
if (key === 'isActive')
type = 'boolean';
if (key === 'level')
type = 'integer';
alterations.push(`ALTER TABLE "${newEntity.tableName}" ADD COLUMN "${value}" ${type};`);
}
}
for (const value of oldFields) {
if (!newFields.has(value)) {
alterations.push(`ALTER TABLE "${newEntity.tableName}" DROP COLUMN "${value}";`);
}
}
}
generateIndexes(config) {
const indexes = [];
const { database } = config;
const permissionNameField = database.entities.permissions.fields.name;
if (permissionNameField) {
indexes.push(`CREATE INDEX "IDX_${database.entities.permissions.tableName}_name" ON "${database.entities.permissions.tableName}" ("${permissionNameField}");`);
}
const routeField = database.entities.routerPermissions.fields.route;
if (routeField) {
indexes.push(`CREATE INDEX "IDX_${database.entities.routerPermissions.tableName}_route" ON "${database.entities.routerPermissions.tableName}" ("${routeField}");`);
}
const userIdField = database.entities.userPermissions.fields.userId;
if (userIdField) {
indexes.push(`CREATE INDEX "IDX_${database.entities.userPermissions.tableName}_user" ON "${database.entities.userPermissions.tableName}" ("${userIdField}");`);
}
return indexes;
}
generateDownMigration(oldConfig, alterations, indexes) {
const downStatements = [];
indexes.forEach((index) => {
const match = index.match(/CREATE INDEX "([^"]+)"/);
if (match) {
downStatements.push(`DROP INDEX "${match[1]}";`);
}
});
alterations.reverse().forEach((alter) => {
if (alter.includes('ADD COLUMN')) {
const match = alter.match(/ADD COLUMN "([^"]+)"/);
if (match) {
downStatements.push(`ALTER TABLE "${oldConfig.database.entities.permissions.tableName}" DROP COLUMN "${match[1]}";`);
}
}
else if (alter.includes('DROP COLUMN')) {
downStatements.push('-- Cannot automatically restore dropped columns');
}
else if (alter.includes('RENAME TO')) {
const match = alter.match(/RENAME TO "([^"]+)"/);
if (match) {
downStatements.push(`ALTER TABLE "${match[1]}" RENAME TO "${oldConfig.database.entities.permissions.tableName}";`);
}
}
});
return downStatements.map((stmt) => `await queryRunner.query(\`${stmt}\`);`).join('\n ');
}
};
exports.MigrationGeneratorService = MigrationGeneratorService;
exports.MigrationGeneratorService = MigrationGeneratorService = __decorate([
(0, common_1.Injectable)()
], MigrationGeneratorService);
//# sourceMappingURL=migration-generator.service.js.map