"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MigrationRunCommand = void 0;
const tslib_1 = require("tslib");
function __assignType(fn, args) {
    fn.__type = args;
    return fn;
}
const app_1 = require("@deepkit/app");
const fs_1 = require("fs");
const lodash_1 = require("lodash");
const path_1 = tslib_1.__importDefault(require("path"));
const state_1 = require("../../app/state");
const services_1 = require("../../services");
const queue_1 = require("../../services/worker/queue");
const common_1 = require("../common");
const dialect_1 = require("../dialect");
const helpers_1 = require("./helpers");
const migration_entity_1 = require("./migration.entity");
let MigrationRunCommand = class MigrationRunCommand {
    constructor(dbProvider) {
        this.dbProvider = dbProvider;
        this.logger = (0, services_1.createLogger)('Migrator');
        this.migrationsDir = (0, helpers_1.getMigrationsDir)();
    }
    async execute() {
        if (!(0, fs_1.existsSync)(this.migrationsDir)) {
            throw new Error('Migrations directory does not exist');
        }
        const migrations = (0, fs_1.readdirSync)(this.migrationsDir)
            .filter(__assignType(f => /\.[jt]s$/.test(f), ['f', '', 'P"2!"/"']))
            .map(__assignType(f => f.replace(/\.[jt]s$/, ''), ['f', '', 'P"2!"/"']));
        migrations.sort();
        this.logger.info(`${migrations.length} migrations found in package`);
        await this.createMigrationsTableIfNotExists();
        this.dbProvider.db.registerEntity(migration_entity_1.MigrationEntity);
        const executedMigrations = await migration_entity_1.MigrationEntity.query().select('name').findField('name');
        const unexecutedMigrations = (0, lodash_1.difference)(migrations, executedMigrations);
        this.logger.info(`${executedMigrations.length} migrations previously executed`);
        this.logger.info(`${unexecutedMigrations.length} migrations to run`);
        const executedAt = new Date();
        for (const migration of unexecutedMigrations) {
            const startTs = Date.now();
            this.logger.info(`Running migration: ${migration}`);
            await this.runCodeMigration(migration);
            this.logger.info(`Completed migration: ${migration}`);
            await (0, common_1.createPersistedEntity)(migration_entity_1.MigrationEntity, {
                executedAt,
                name: migration,
                durationMs: Date.now() - startTs
            });
        }
        // this may seem out of place but it makes sense to do during a migration, since
        // the application is about to start and will re-register all jobs with the new schedule
        if (state_1.globalState.enableWorker) {
            const queue = queue_1.WorkerQueueRegistry.getDefaultQueue();
            const repeatableJobs = await queue.getRepeatableJobs();
            for (const job of repeatableJobs) {
                this.logger.info('Removing repeatable job', { job: { name: job.name, key: job.key } });
                await queue.removeRepeatableByKey(job.key);
            }
            await queue_1.WorkerQueueRegistry.closeQueues();
        }
    }
    async createMigrationsTableIfNotExists() {
        const dialect = (0, dialect_1.getDialect)(this.dbProvider.db.adapter);
        const tableInfoRows = await this.dbProvider.db.rawQuery((0, dialect_1.tableExistsSql)(dialect, '_migrations'));
        if (tableInfoRows.length)
            return;
        if (dialect === 'postgres') {
            await this.dbProvider.db.rawQuery(`
                CREATE TABLE "_migrations" (
                    "name" varchar(255) NOT NULL PRIMARY KEY,
                    "executedAt" timestamp NOT NULL,
                    "durationMs" integer NOT NULL
                )
            `);
        }
        else {
            await this.dbProvider.db.rawQuery(`
                CREATE TABLE _migrations (
                    name varchar(255) NOT NULL,
                    executedAt datetime NOT NULL,
                    durationMs int unsigned NOT NULL,
                    PRIMARY KEY (name)
                ) ENGINE=InnoDB
            `);
        }
    }
    async runCodeMigration(file) {
        let migrationModule;
        try {
            // eslint-disable-next-line @typescript-eslint/no-require-imports
            migrationModule = require(path_1.default.join(process.cwd(), this.migrationsDir, file));
        }
        catch (err) {
            this.logger.error('Failed to load migration', { file });
            throw err;
        }
        if (!('default' in migrationModule) || typeof migrationModule.default !== 'function') {
            throw new Error(`Migration ${file} does not export a function`);
        }
        try {
            await migrationModule.default(this.dbProvider.db);
        }
        catch (err) {
            this.logger.error('Migration function failed to execute', err, { file });
            throw err;
        }
    }
};
exports.MigrationRunCommand = MigrationRunCommand;
MigrationRunCommand.__type = ['logger', function () { return (0, services_1.createLogger)('Migrator'); }, 'migrationsDir', function () { return (0, helpers_1.getMigrationsDir)(); }, () => state_1.DBProvider, 'dbProvider', 'constructor', 'execute', 'createMigrationsTableIfNotExists', 'file', 'runCodeMigration', 'MigrationRunCommand', '!3!;>"!3#;>$PP7%2&;"0\'P"0(P"0)P&2*"0+5w,'];
exports.MigrationRunCommand = MigrationRunCommand = tslib_1.__decorate([
    app_1.cli.controller('migration:run'),
    tslib_1.__metadata("design:paramtypes", [state_1.DBProvider])
], MigrationRunCommand);
//# sourceMappingURL=MigrationRunCommand.js.map