"use strict";
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 __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.DatabaseService = void 0;
const common_1 = require("@nestjs/common");
const semver_1 = __importDefault(require("semver"));
const constants_1 = require("../constants");
const decorators_1 = require("../decorators");
const enum_1 = require("../enum");
const base_service_1 = require("./base.service");
const messages = {
    notInstalled: (name) => `The ${name} extension is not available in this Postgres instance.
    If using a container image, ensure the image has the extension installed.`,
    nightlyVersion: ({ name, extension, version }) => `
    The ${name} extension version is ${version}, which means it is a nightly release.

    Please run 'DROP EXTENSION IF EXISTS ${extension}' and switch to a release version.
    See https://docs.immich.app/guides/database-queries for how to query the database.`,
    outOfRange: ({ name, version, range }) => `The ${name} extension version is ${version}, but Immich only supports ${range}.
    Please change ${name} to a compatible version in the Postgres instance.`,
    createFailed: ({ name, extension }) => `Failed to activate ${name} extension.
    Please ensure the Postgres instance has ${name} installed.

    If the Postgres instance already has ${name} installed, Immich may not have the necessary permissions to activate it.
    In this case, please run 'CREATE EXTENSION IF NOT EXISTS ${extension} CASCADE' manually as a superuser.
    See https://docs.immich.app/guides/database-queries for how to query the database.`,
    updateFailed: ({ name, extension, availableVersion }) => `The ${name} extension can be updated to ${availableVersion}.
    Immich attempted to update the extension, but failed to do so.
    This may be because Immich does not have the necessary permissions to update the extension.

    Please run 'ALTER EXTENSION ${extension} UPDATE' manually as a superuser.
    See https://docs.immich.app/guides/database-queries for how to query the database.`,
    dropFailed: ({ name, extension }) => `The ${name} extension is no longer needed, but could not be dropped.
    This may be because Immich does not have the necessary permissions to drop the extension.

    Please run 'DROP EXTENSION ${extension};' manually as a superuser.
    See https://docs.immich.app/guides/database-queries for how to query the database.`,
    invalidDowngrade: ({ name, installedVersion, availableVersion }) => `The database currently has ${name} ${installedVersion} activated, but the Postgres instance only has ${availableVersion} available.
    This most likely means the extension was downgraded.
    If ${name} ${installedVersion} is compatible with Immich, please ensure the Postgres instance has this available.`,
};
let DatabaseService = class DatabaseService extends base_service_1.BaseService {
    async onBootstrap() {
        const version = await this.databaseRepository.getPostgresVersion();
        const current = semver_1.default.coerce(version);
        const postgresRange = this.databaseRepository.getPostgresVersionRange();
        if (!current || !semver_1.default.satisfies(current, postgresRange)) {
            throw new Error(`Invalid PostgreSQL version. Found ${version}, but needed ${postgresRange}. Please use a supported version.`);
        }
        await this.databaseRepository.withLock(enum_1.DatabaseLock.Migrations, async () => {
            const extension = await this.databaseRepository.getVectorExtension();
            const name = constants_1.EXTENSION_NAMES[extension];
            const extensionRange = this.databaseRepository.getExtensionVersionRange(extension);
            const extensionVersions = await this.databaseRepository.getExtensionVersions(constants_1.VECTOR_EXTENSIONS);
            const { installedVersion, availableVersion } = extensionVersions.find((v) => v.name === extension) ?? {};
            if (!availableVersion) {
                throw new Error(messages.notInstalled(name));
            }
            if ([availableVersion, installedVersion].some((version) => version && semver_1.default.eq(version, '0.0.0'))) {
                throw new Error(messages.nightlyVersion({ name, extension, version: '0.0.0' }));
            }
            if (!semver_1.default.satisfies(availableVersion, extensionRange)) {
                throw new Error(messages.outOfRange({ name, extension, version: availableVersion, range: extensionRange }));
            }
            if (!installedVersion) {
                await this.createExtension(extension);
            }
            if (installedVersion && semver_1.default.gt(availableVersion, installedVersion)) {
                await this.updateExtension(extension, availableVersion);
            }
            else if (installedVersion && !semver_1.default.satisfies(installedVersion, extensionRange)) {
                throw new Error(messages.outOfRange({ name, extension, version: installedVersion, range: extensionRange }));
            }
            else if (installedVersion && semver_1.default.lt(availableVersion, installedVersion)) {
                throw new Error(messages.invalidDowngrade({ name, extension, availableVersion, installedVersion }));
            }
            try {
                await this.databaseRepository.reindexVectorsIfNeeded([enum_1.VectorIndex.Clip, enum_1.VectorIndex.Face]);
            }
            catch (error) {
                this.logger.warn('Could not run vector reindexing checks. If the extension was updated, please restart the Postgres instance. If you are upgrading directly from a version below 1.107.2, please upgrade to 1.107.2 first.');
                throw error;
            }
            for (const { name: dbName, installedVersion } of extensionVersions) {
                const isDepended = dbName === enum_1.DatabaseExtension.Vector && extension === enum_1.DatabaseExtension.VectorChord;
                if (dbName !== extension && installedVersion && !isDepended) {
                    await this.dropExtension(dbName);
                }
            }
            const { database } = this.configRepository.getEnv();
            if (!database.skipMigrations) {
                await this.databaseRepository.runMigrations();
                this.logger.log('Checking for schema drift');
                const drift = await this.databaseRepository.getSchemaDrift();
                if (drift.items.length === 0) {
                    this.logger.log('No schema drift detected');
                }
                else {
                    this.logger.warn(`${constants_1.ErrorMessages.SchemaDrift} or run \`immich-admin schema-check\``);
                    for (const warning of drift.asHuman()) {
                        this.logger.warn(`  - ${warning}`);
                    }
                }
            }
            await Promise.all([
                this.databaseRepository.prewarm(enum_1.VectorIndex.Clip),
                this.databaseRepository.prewarm(enum_1.VectorIndex.Face),
            ]);
        });
    }
    async createExtension(extension) {
        try {
            await this.databaseRepository.createExtension(extension);
        }
        catch (error) {
            const name = constants_1.EXTENSION_NAMES[extension];
            this.logger.fatal(messages.createFailed({ name, extension }));
            throw error;
        }
    }
    async updateExtension(extension, availableVersion) {
        this.logger.log(`Updating ${constants_1.EXTENSION_NAMES[extension]} extension to ${availableVersion}`);
        try {
            await this.databaseRepository.updateVectorExtension(extension, availableVersion);
        }
        catch (error) {
            this.logger.warn(messages.updateFailed({ name: constants_1.EXTENSION_NAMES[extension], extension, availableVersion }));
            throw error;
        }
    }
    async dropExtension(extension) {
        try {
            await this.databaseRepository.dropExtension(extension);
        }
        catch (error) {
            const name = constants_1.EXTENSION_NAMES[extension];
            this.logger.warn(messages.dropFailed({ name, extension }), error);
        }
    }
};
exports.DatabaseService = DatabaseService;
__decorate([
    (0, decorators_1.OnEvent)({ name: 'AppBootstrap', priority: enum_1.BootstrapEventPriority.DatabaseService }),
    __metadata("design:type", Function),
    __metadata("design:paramtypes", []),
    __metadata("design:returntype", Promise)
], DatabaseService.prototype, "onBootstrap", null);
exports.DatabaseService = DatabaseService = __decorate([
    (0, common_1.Injectable)()
], DatabaseService);
//# sourceMappingURL=database.service.js.map