"use strict";
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 __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.CRUDService = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const class_validator_1 = require("class-validator");
const user_entity_1 = require("../entities/user.entity");
const setting_service_1 = require("./setting.service");
const error_messages_1 = require("../constants/error-messages");
const success_messages_1 = require("../constants/success-messages");
const typeorm_1 = require("typeorm");
const create_field_metadata_dto_1 = require("../dtos/create-field-metadata.dto");
const model_metadata_entity_1 = require("../entities/model-metadata.entity");
const BigIntFieldCrudManager_1 = require("../helpers/field-crud-managers/BigIntFieldCrudManager");
const BooleanFieldCrudManager_1 = require("../helpers/field-crud-managers/BooleanFieldCrudManager");
const ComputedFieldCrudManager_1 = require("../helpers/field-crud-managers/ComputedFieldCrudManager");
const DateFieldCrudManager_1 = require("../helpers/field-crud-managers/DateFieldCrudManager");
const DecimalFieldCrudManager_1 = require("../helpers/field-crud-managers/DecimalFieldCrudManager");
const EmailFieldCrudManager_1 = require("../helpers/field-crud-managers/EmailFieldCrudManager");
const IntFieldCrudManager_1 = require("../helpers/field-crud-managers/IntFieldCrudManager");
const JsonFieldCrudManager_1 = require("../helpers/field-crud-managers/JsonFieldCrudManager");
const LongTextFieldCrudManager_1 = require("../helpers/field-crud-managers/LongTextFieldCrudManager");
const ManyToManyRelationFieldCrudManager_1 = require("../helpers/field-crud-managers/ManyToManyRelationFieldCrudManager");
const ManyToOneRelationFieldCrudManager_1 = require("../helpers/field-crud-managers/ManyToOneRelationFieldCrudManager");
const MediaFieldCrudManager_1 = require("../helpers/field-crud-managers/MediaFieldCrudManager");
const NoOpsFieldCrudManager_1 = require("../helpers/field-crud-managers/NoOpsFieldCrudManager");
const OneToManyRelationFieldCrudManager_1 = require("../helpers/field-crud-managers/OneToManyRelationFieldCrudManager");
const PasswordFieldCrudManager_1 = require("../helpers/field-crud-managers/PasswordFieldCrudManager");
const RichTextFieldCrudManager_1 = require("../helpers/field-crud-managers/RichTextFieldCrudManager");
const SelectionDynamicFieldCrudManager_1 = require("../helpers/field-crud-managers/SelectionDynamicFieldCrudManager");
const SelectionStaticFieldCrudManager_1 = require("../helpers/field-crud-managers/SelectionStaticFieldCrudManager");
const ShortTextFieldCrudManager_1 = require("../helpers/field-crud-managers/ShortTextFieldCrudManager");
const UUIDFieldCrudManager_1 = require("../helpers/field-crud-managers/UUIDFieldCrudManager");
const crud_helper_service_1 = require("./crud-helper.service");
const hashing_service_1 = require("./hashing.service");
const solid_registry_1 = require("../helpers/solid-registry");
const mediaStorageProviders_1 = require("./mediaStorageProviders");
const model_metadata_service_1 = require("./model-metadata.service");
const request_context_service_1 = require("./request-context.service");
class CRUDService {
    constructor(entityManager, repo, modelName, moduleName, moduleRef, defaultDatasourceEntityManager) {
        this.entityManager = entityManager;
        this.repo = repo;
        this.modelName = modelName;
        this.moduleName = moduleName;
        this.moduleRef = moduleRef;
        this.defaultDatasourceEntityManager = defaultDatasourceEntityManager;
    }
    get modelMetadataService() {
        return this._modelMetadataService ??= this.moduleRef.get(model_metadata_service_1.ModelMetadataService, { strict: false });
    }
    get crudHelperService() {
        return this._crudHelperService ??= this.moduleRef.get(crud_helper_service_1.CrudHelperService, { strict: false });
    }
    get discoveryService() {
        return this._discoveryService ??= this.moduleRef.get(core_1.DiscoveryService, { strict: false });
    }
    get settingService() {
        return this._settingService ??= this.moduleRef.get(setting_service_1.SettingService, { strict: false });
    }
    async tryCreateAsExtensionUser(createDto) {
        if (this.repo.metadata?.parentEntityMetadata?.target !== user_entity_1.User)
            return null;
        const registry = this.moduleRef.get(solid_registry_1.SolidRegistry, { strict: false });
        if (!registry?.getExtensionUserCreationProvider()) {
            throw new common_1.InternalServerErrorException(`No ExtensionUserCreationProvider registered. Register one to create ${this.repo.metadata.name} entities.`);
        }
        const { AuthenticationService } = await Promise.resolve().then(() => __importStar(require('./authentication.service')));
        const authService = this.moduleRef.get(AuthenticationService, { strict: false });
        return authService.signUp(createDto);
    }
    async create(createDto, files = [], solidRequestContext = {}) {
        const asExtensionUser = await this.tryCreateAsExtensionUser(createDto);
        if (asExtensionUser !== null)
            return asExtensionUser;
        let hasMediaFields = false;
        const model = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasCreatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const fieldsToProcess = [...model.fields];
        for (const field of fieldsToProcess) {
            const transformed = await this.validateAndTransformDto(field, createDto, files, hasMediaFields);
            createDto = transformed.dto;
            hasMediaFields = transformed.hasMediaFields;
        }
        ;
        try {
            const entity = this.repo.create(createDto);
            const savedEntity = await this.repo.save(entity);
            if (hasMediaFields) {
                await this.saveMedia(model, files, savedEntity);
            }
            return savedEntity;
        }
        catch (error) {
            if (error instanceof typeorm_1.QueryFailedError && error.message.includes('duplicate key value violates unique constraint')) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.DUPLICATE_ENTRY);
            }
            throw error;
        }
    }
    async loadModel() {
        return await this.modelMetadataService.findOneBySingularName(this.modelName, {
            fields: {
                model: {
                    userKeyField: true
                },
                mediaStorageProvider: true,
            },
            module: true,
        });
    }
    async validateAndTransformDto(field, dto, files, hasMediaFields, isPartialUpdate = false, isUpdate = false, entityId) {
        const fieldManager = await this.fieldCrudManager(field, this.entityManager, isPartialUpdate, isUpdate, entityId);
        const validationErrors = fieldManager.validate(dto, files);
        const errors = (validationErrors instanceof Promise) ? await validationErrors : validationErrors;
        if (errors.length > 0) {
            throw new common_1.BadRequestException(`Validation errors in ${field.name} is invalid i.e ${errors.map(e => e.error).join(', ')}`);
        }
        const dtoOrPromise = fieldManager.transformForCreate(dto);
        dto = (dtoOrPromise instanceof Promise) ? await dtoOrPromise : dtoOrPromise;
        hasMediaFields = hasMediaFields || field.type === 'mediaSingle' || field.type === 'mediaMultiple';
        return { dto, hasMediaFields };
    }
    async saveMedia(model, files, savedEntity) {
        const mediaFields = model.fields.filter(field => field.type === 'mediaSingle' || field.type === 'mediaMultiple');
        await Promise.all(mediaFields.map(async (mediaField) => {
            const media = files.filter(multerFile => multerFile.fieldname === mediaField.name);
            if (media.length > 0) {
                const storageProviderMetadata = mediaField.mediaStorageProvider;
                const storageProviderType = storageProviderMetadata.type;
                const storageProvider = await (0, mediaStorageProviders_1.getMediaStorageProvider)(this.moduleRef, storageProviderType);
                await storageProvider.store(media, savedEntity, mediaField);
            }
        }));
    }
    async update(id, updateDto, files = [], isPartialUpdate = false, solidRequestContext = {}, isUpdate = false) {
        if (!id) {
            throw new Error(error_messages_1.ERROR_MESSAGES.ID_REQUIRED_FOR_UPDATE);
        }
        isUpdate = true;
        const model = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasUpdatePermissionOnModel(solidRequestContext.activeUser, model.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const entity = await this.repo.findOne({
            where: {
                id: id,
            },
        });
        if (!entity) {
            throw new Error(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
        }
        if (model.draftPublishWorkflow === true && entity.publishedAt) {
            throw new common_1.BadRequestException(`Cannot update a published record for model ${this.modelName}. Unpublish it first.`);
        }
        let hasMediaFields = false;
        const fieldsToProcess = [...model.fields];
        for (const field of fieldsToProcess) {
            const transformed = await this.validateAndTransformDto(field, updateDto, files, hasMediaFields, isPartialUpdate, isUpdate, id);
            updateDto = transformed.dto;
            hasMediaFields = transformed.hasMediaFields;
        }
        const mergedEntity = this.repo.merge(entity, updateDto);
        const savedEntity = await this.repo.save(mergedEntity);
        if (hasMediaFields) {
            await this.saveMedia(model, files, savedEntity);
        }
        return savedEntity;
    }
    async delete(id, solidRequestContext = {}) {
        if (!id) {
            throw new Error(error_messages_1.ERROR_MESSAGES.ID_REQUIRED_FOR_DELETE);
        }
        const loadedmodel = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasDeletePermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const model = await this.modelMetadataService.findOneBySingularName(this.modelName, {
            fields: {
                model: true,
                mediaStorageProvider: true,
            },
            module: true,
        });
        const entity = await this.repo.findOne({
            where: {
                id: id,
            },
        });
        if (!entity) {
            throw new Error(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
        }
        if (model.draftPublishWorkflow === true && entity.publishedAt) {
            throw new common_1.BadRequestException(`Cannot update a published record for model ${this.modelName}, Unpublish it first.`);
        }
        if (model.internationalisation) {
            const childEntities = await this.repo.find({
                where: { defaultEntityLocaleId: id }
            });
            if (childEntities.length > 0) {
                if (model.enableSoftDelete === true) {
                    await this.repo.softRemove(childEntities);
                }
                else {
                    await this.repo.remove(childEntities);
                }
            }
        }
        if (model.enableSoftDelete === true) {
            await this.repo.softRemove(entity);
            return this.repo.save(entity);
        }
        else {
            return this.repo.remove(entity);
        }
    }
    async fieldCrudManager(fieldMetadata, entityManager, isPartialUpdate = false, isUpdate = false, entityId) {
        const commonOptions = { required: fieldMetadata.required && !isPartialUpdate, fieldName: fieldMetadata.name, isUpdate };
        switch (fieldMetadata.type) {
            case create_field_metadata_dto_1.SolidFieldType.shortText: {
                const options = { ...commonOptions, length: fieldMetadata.max, regexPattern: fieldMetadata.regexPattern };
                return new ShortTextFieldCrudManager_1.ShortTextFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.longtext: {
                const options = { ...commonOptions, regexPattern: fieldMetadata.regexPattern };
                return new LongTextFieldCrudManager_1.LongTextFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.boolean: {
                const options = { ...commonOptions };
                return new BooleanFieldCrudManager_1.BooleanFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.richText: {
                const options = { ...commonOptions, regexPattern: fieldMetadata.regexPattern };
                return new RichTextFieldCrudManager_1.RichTextFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.json: {
                const options = { ...commonOptions };
                return new JsonFieldCrudManager_1.JsonFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.int: {
                const options = { ...commonOptions, min: fieldMetadata.min, max: fieldMetadata.max };
                return new IntFieldCrudManager_1.IntFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.decimal: {
                const options = { ...commonOptions, min: fieldMetadata.min, max: fieldMetadata.max };
                return new DecimalFieldCrudManager_1.DecimalFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.bigint: {
                const options = { ...commonOptions, min: fieldMetadata.min, max: fieldMetadata.max };
                return new BigIntFieldCrudManager_1.BigIntFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.email: {
                const options = { ...commonOptions, max: fieldMetadata.max ?? EmailFieldCrudManager_1.MAX_EMAIL_LENGTH, regexPattern: fieldMetadata.regexPattern };
                return new EmailFieldCrudManager_1.EmailFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.date:
            case create_field_metadata_dto_1.SolidFieldType.datetime: {
                const options = { ...commonOptions };
                return new DateFieldCrudManager_1.DateFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.password: {
                const options = { ...commonOptions, min: fieldMetadata.min, max: fieldMetadata.max, regexPattern: fieldMetadata.regexPattern, hashingService: this.moduleRef.get(hashing_service_1.HashingService, { strict: false }) };
                return new PasswordFieldCrudManager_1.PasswordFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.mediaSingle:
            case create_field_metadata_dto_1.SolidFieldType.mediaMultiple: {
                const options = {
                    ...commonOptions,
                    mediaMaxSizeKb: fieldMetadata.mediaMaxSizeKb,
                    mediaTypes: fieldMetadata.mediaTypes,
                    type: fieldMetadata.type
                };
                return new MediaFieldCrudManager_1.MediaFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.relation: {
                if (fieldMetadata.relationType === create_field_metadata_dto_1.RelationType.manyToOne) {
                    const relationCoModelUserKeyFieldName = await this.getUserKeyFieldNameForModel(fieldMetadata.relationCoModelSingularName);
                    const manyToOneOptions = {
                        ...commonOptions,
                        relationCoModelSingularName: fieldMetadata.relationCoModelSingularName,
                        modelSingularName: fieldMetadata.model.singularName,
                        relationCoModelUserKeyFieldName: relationCoModelUserKeyFieldName,
                        entityManager,
                    };
                    return new ManyToOneRelationFieldCrudManager_1.ManyToOneRelationFieldCrudManager(manyToOneOptions);
                }
                else if (fieldMetadata.relationType === create_field_metadata_dto_1.RelationType.oneToMany) {
                    const oneToManyOptions = {
                        ...commonOptions,
                        relationCoModelSingularName: fieldMetadata.relationCoModelSingularName,
                        modelSingularName: fieldMetadata.model.singularName,
                        entityManager,
                        inverseFieldName: fieldMetadata.relationCoModelFieldName,
                        inverseRelationCoModelFieldName: fieldMetadata.name,
                        entityId,
                    };
                    return new OneToManyRelationFieldCrudManager_1.OneToManyRelationFieldCrudManager(oneToManyOptions);
                }
                else if (fieldMetadata.relationType === create_field_metadata_dto_1.RelationType.manyTomany) {
                    if (fieldMetadata.isRelationManyToManyOwner) {
                        const manyToManyOptions = {
                            ...commonOptions,
                            relationCoModelSingularName: fieldMetadata.relationCoModelSingularName,
                            modelSingularName: fieldMetadata.model.singularName,
                            isInverseSide: false,
                            entityManager,
                            fieldName: fieldMetadata.name,
                            entityId,
                        };
                        return new ManyToManyRelationFieldCrudManager_1.ManyToManyRelationFieldCrudManager(manyToManyOptions);
                    }
                    else {
                        const inverseManyToManyOptions = {
                            ...commonOptions,
                            relationCoModelSingularName: fieldMetadata.relationCoModelSingularName,
                            modelSingularName: fieldMetadata.model.singularName,
                            isInverseSide: true,
                            entityManager,
                            fieldName: fieldMetadata.relationCoModelFieldName,
                            relationCoModelFieldName: fieldMetadata.name,
                            entityId,
                        };
                        return new ManyToManyRelationFieldCrudManager_1.ManyToManyRelationFieldCrudManager(inverseManyToManyOptions);
                    }
                }
                else
                    throw new Error(error_messages_1.ERROR_MESSAGES.RELATION_TYPE_NOT_SUPPORTED);
            }
            case create_field_metadata_dto_1.SolidFieldType.selectionStatic: {
                const options = { ...commonOptions, selectionStaticValues: fieldMetadata.selectionStaticValues, selectionValueType: fieldMetadata.selectionValueType, isMultiSelect: fieldMetadata.isMultiSelect };
                return new SelectionStaticFieldCrudManager_1.SelectionStaticFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.selectionDynamic: {
                const options = { ...commonOptions, selectionDynamicProvider: fieldMetadata.selectionDynamicProvider, selectionDynamicProviderCtxt: JSON.parse(fieldMetadata.selectionDynamicProviderCtxt), selectionValueType: fieldMetadata.selectionValueType, discoveryService: this.discoveryService, isMultiSelect: fieldMetadata.isMultiSelect };
                return new SelectionDynamicFieldCrudManager_1.SelectionDynamicFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.uuid: {
                const options = { ...commonOptions };
                return new UUIDFieldCrudManager_1.UUIDFieldCrudManager(options);
            }
            case create_field_metadata_dto_1.SolidFieldType.computed: {
                const options = { ...commonOptions, computedFieldProvider: fieldMetadata.computedFieldValueProvider, computedFieldValueProviderCtxt: fieldMetadata.computedFieldValueProviderCtxt, computedFieldValueType: fieldMetadata.computedFieldValueType, discoveryService: this.discoveryService, skipComputation: this.isSkipComputation(isPartialUpdate, fieldMetadata) };
                return new ComputedFieldCrudManager_1.ComputedFieldCrudManager(options);
            }
            default:
                return new NoOpsFieldCrudManager_1.NoOpsFieldCrudManager();
        }
    }
    isSkipComputation(isPartialUpdate, computedFieldMetadata) {
        if (isPartialUpdate)
            return true;
        if (computedFieldMetadata.computedFieldTriggerConfig && computedFieldMetadata.computedFieldTriggerConfig.length > 0) {
            return true;
        }
        return false;
    }
    async find(basicFilterDto, solidRequestContext = {}) {
        const alias = 'entity';
        let { limit, offset, populateMedia, populateGroup, groupFilter } = basicFilterDto;
        const populateUserIdFields = this.crudHelperService.extractUserIdFieldsFromPopulate(basicFilterDto.populate);
        const { singularName, internationalisation, draftPublishWorkflow } = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasReadPermissionOnModel(solidRequestContext.activeUser, singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException('Forbidden');
            }
        }
        const requestContextService = this.moduleRef.get(request_context_service_1.RequestContextService, { strict: false });
        requestContextService.setRequestFilter(basicFilterDto);
        var qb = await this.repo.createSecurityRuleAwareQueryBuilder(alias);
        if (basicFilterDto.groupBy) {
            const groupFilterQb = (internationalisation && draftPublishWorkflow)
                ? this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias, internationalisation, draftPublishWorkflow, this.moduleRef, crud_helper_service_1.FilterCombinator.AND, false, false)
                : this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias, undefined, undefined, undefined, crud_helper_service_1.FilterCombinator.AND, false, false);
            const groupByFields = this.crudHelperService.normalize(basicFilterDto.groupBy);
            if (!groupByFields.length) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
            }
            if (basicFilterDto.populateGroup) {
                const hasRelationGroup = groupByFields.some(field => field.includes('.'));
                if (hasRelationGroup) {
                    throw new common_1.BadRequestException('populateGroup is not supported when grouping on relation fields. Fetch group metadata first and retrieve records in a separate call.');
                }
            }
            const { aliasMap: groupAliasMap, formatMap: groupFormatMap, expressionMap: groupExpressionMap } = this.crudHelperService.applyGroupBySelections(groupFilterQb, groupByFields, alias);
            const aggregateAliasMap = this.crudHelperService.applyAggregates(groupFilterQb, basicFilterDto.aggregates, alias);
            const sortAliasMap = { ...groupAliasMap, ...aggregateAliasMap };
            this.crudHelperService.applyGroupSortingAndPagination(groupFilterQb, basicFilterDto.sort, sortAliasMap, limit, offset);
            const groupByResult = await groupFilterQb.getRawMany();
            const totalGroups = await this.crudHelperService.countGroups(groupFilterQb);
            const groupByFieldsOrdered = this.crudHelperService.normalize(basicFilterDto.groupBy || []);
            const { groupMeta, groupRecords } = await this.handleGroupFind(groupByResult, groupFilter, populateGroup, alias, populateUserIdFields, populateMedia, basicFilterDto, groupAliasMap, aggregateAliasMap, groupByFieldsOrdered, groupFormatMap, groupExpressionMap);
            return {
                meta: {
                    "totalRecords": totalGroups
                },
                groupMeta,
                groupRecords,
            };
        }
        else {
            qb = (internationalisation && draftPublishWorkflow)
                ? this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias, internationalisation, draftPublishWorkflow, this.moduleRef)
                : this.crudHelperService.buildFilterQuery(qb, basicFilterDto, alias);
            const { meta, records } = await this.handleNonGroupFind(qb, populateUserIdFields, populateMedia, offset, limit, alias);
            return {
                meta,
                records,
            };
        }
    }
    async handleNonGroupFind(qb, populateUserIdFields, populateMedia, offset, limit, alias) {
        const [entities, count] = await qb.getManyAndCount();
        if (populateUserIdFields && populateUserIdFields.length > 0) {
            await this.handlePopulateUserIdFields(populateUserIdFields, entities);
        }
        if (populateMedia && populateMedia.length > 0) {
            await this.handlePopulateMedia(populateMedia, entities);
        }
        return this.wrapFindResponse(offset, limit, count, entities);
    }
    async handleGroupFind(groupByResult, groupFilter, populateGroup, alias, populateUserIdFields, populateMedia, baseFilterDto, groupAliasMap, aggregateAliasMap, groupByFieldsOrdered, groupFormatMap, groupExpressionMap) {
        const groupMeta = [];
        const groupRecords = [];
        const aggregateAliasSet = new Set(Object.values(aggregateAliasMap));
        for (const group of groupByResult) {
            if (populateGroup) {
                let groupByQb = await this.repo.createSecurityRuleAwareQueryBuilder(alias);
                const groupFilterDto = {
                    ...baseFilterDto,
                    ...groupFilter,
                    groupBy: undefined,
                    aggregates: undefined,
                    sort: groupFilter?.sort,
                };
                groupByQb = this.crudHelperService.buildFilterQuery(groupByQb, groupFilterDto, alias);
                groupByQb = this.crudHelperService.buildGroupByRecordsQuery(groupByQb, group, alias, groupAliasMap, aggregateAliasMap, groupExpressionMap);
                const [entities, count] = await groupByQb.getManyAndCount();
                if (populateUserIdFields && populateUserIdFields.length > 0) {
                    await this.handlePopulateUserIdFields(populateUserIdFields, entities);
                }
                if (populateMedia && populateMedia.length > 0) {
                    await this.handlePopulateMedia(populateMedia, entities);
                }
                const groupData = this.wrapFindResponse(groupFilter?.offset, groupFilter?.limit, count, entities);
                groupRecords.push(this.crudHelperService.createGroupRecords(group, aggregateAliasSet, groupData, groupByFieldsOrdered, groupAliasMap, groupFormatMap));
            }
            groupMeta.push(this.crudHelperService.createGroupMeta(group, aggregateAliasSet, groupByFieldsOrdered, groupAliasMap, groupFormatMap));
        }
        return { groupMeta, groupRecords };
    }
    wrapFindResponse(offset, limit, count, entities) {
        const safeLimit = limit ?? count ?? 0;
        const safeOffset = offset ?? 0;
        const currentPage = safeLimit ? Math.floor(safeOffset / safeLimit) + 1 : 1;
        const totalPages = safeLimit ? Math.ceil(count / safeLimit) : 1;
        const nextPage = safeLimit && currentPage < totalPages ? currentPage + 1 : null;
        const prevPage = safeLimit && currentPage > 1 ? currentPage - 1 : null;
        const r = {
            meta: {
                totalRecords: count,
                currentPage: currentPage,
                nextPage: nextPage,
                prevPage: prevPage,
                totalPages: totalPages,
                perPage: safeLimit ? +safeLimit : 0,
            },
            records: entities
        };
        return r;
    }
    async handlePopulateUserIdFields(userIdFields, entities) {
        const userRepository = this.entityManager.getRepository(user_entity_1.User);
        for (const entity of entities) {
            for (const userFieldPath of userIdFields) {
                const userId = entity[userFieldPath];
                if (userId) {
                    const user = await userRepository.findOne({
                        where: { id: userId },
                    });
                    entity[userFieldPath] = user;
                }
            }
        }
    }
    async handlePopulateMedia(populateMedia, entities) {
        const model = await this.getDatasourceDefaultEntityManager().getRepository(model_metadata_entity_1.ModelMetadata).findOne({
            where: {
                singularName: this.modelName,
            },
            relations: ['fields', 'fields.mediaStorageProvider', 'fields.model', 'module'],
        });
        for (const entity of entities) {
            for (const mediaFieldPath of populateMedia) {
                await this.populateMediaObject(mediaFieldPath, model, entity);
            }
        }
        return entities;
    }
    async populateMediaObject(mediaFieldPath, model, entity) {
        if (mediaFieldPath.includes('.')) {
            const pathParts = mediaFieldPath.split('.');
            const mediaFieldMetadata = await this.getFieldMetadataRecursively(pathParts, model.fields);
            if (!mediaFieldMetadata) {
                throw new common_1.BadRequestException(`Media field ${mediaFieldPath} not found in model ${this.modelName}`);
            }
            const mediaFieldEntities = this.getMediaFieldEntities(entity, pathParts);
            if (!mediaFieldEntities || mediaFieldEntities.length === 0) {
                return;
            }
            for (const mediaFieldEntity of mediaFieldEntities) {
                const mediaWithFullUrl = await this.getMediaWithFullUrl(mediaFieldEntity, mediaFieldMetadata);
                this.appendMediaKey(mediaWithFullUrl, mediaFieldEntity, mediaFieldMetadata.name);
            }
        }
        else {
            const mediaFieldMetadata = model.fields.find(field => field.name === mediaFieldPath);
            if (!mediaFieldMetadata) {
                throw new common_1.BadRequestException(`Media field ${mediaFieldPath} not found in model ${this.modelName}`);
            }
            const mediaWithFullUrl = await this.getMediaWithFullUrl(entity, mediaFieldMetadata);
            this.appendMediaKey(mediaWithFullUrl, entity, mediaFieldPath);
        }
    }
    appendMediaKey(mediaWithFullUrl, entity, mediaFieldPath) {
        if (entity['_media']) {
            entity['_media'][mediaFieldPath] = mediaWithFullUrl;
        }
        else {
            entity['_media'] = {
                [mediaFieldPath]: mediaWithFullUrl
            };
        }
    }
    getMediaFieldEntities(entity, mediaPathParts) {
        let entityPart = entity;
        for (let i = 0; i < mediaPathParts.length - 1; i++) {
            const pathPart = mediaPathParts[i];
            if (entity[pathPart]) {
                entityPart = entity[pathPart];
            }
            else {
                throw new common_1.BadRequestException(`Media field ${pathPart} not found in entity ${JSON.stringify(entity)}`);
            }
        }
        return (0, class_validator_1.isArray)(entityPart) ? entityPart : [entityPart];
    }
    async getMediaWithFullUrl(mediaEntity, mediaFieldMetadata) {
        const storageProviderMetadata = mediaFieldMetadata.mediaStorageProvider;
        const storageProviderType = storageProviderMetadata.type;
        const storageProvider = await (0, mediaStorageProviders_1.getMediaStorageProvider)(this.moduleRef, storageProviderType);
        const mediaDetails = await storageProvider.retrieve(mediaEntity, mediaFieldMetadata);
        return mediaDetails;
    }
    async findOne(id, query = {}, solidRequestContext = {}) {
        const { populate = [], fields = [], populateMedia = [] } = query;
        const normalizedPopulate = this.crudHelperService.normalize(populate);
        const normalizedPopulateMedia = this.crudHelperService.normalize(populateMedia);
        const additionalPopulate = this.crudHelperService.additionalRelationsRequiredForMediaPopulation(normalizedPopulateMedia);
        normalizedPopulate.push(...additionalPopulate.filter((relation) => !normalizedPopulate.includes(relation)));
        const model = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasReadPermissionOnModel(solidRequestContext.activeUser, model.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        let entity = await this.repo.findOne({
            where: {
                id: id,
            },
            relations: normalizedPopulate,
            select: fields,
        });
        if (!entity) {
            throw new common_1.NotFoundException(`Entity [${this.moduleName}.${this.modelName}] with id ${id} not found`);
        }
        if (normalizedPopulateMedia.length > 0) {
            const populatedEntities = await this.handlePopulateMedia(normalizedPopulateMedia, [entity]);
            entity = populatedEntities[0];
        }
        return entity;
    }
    async createMany(createDtos, solidRequestContext = {}) {
        const loadedmodel = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasCreatePermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const model = await this.modelMetadataService.findOneBySingularName(this.modelName, {
            fields: {
                model: true,
                mediaStorageProvider: true,
            },
            module: true,
        });
        const entitiesForSave = [];
        for (const createDto of createDtos) {
            let transformedDto = createDto;
            for (const field of model.fields) {
                const fieldManager = await this.fieldCrudManager(field, this.entityManager);
                const validationErrors = await fieldManager.validate(createDto, []);
                if (validationErrors.length > 0) {
                    throw new common_1.BadRequestException(`Validation errors in ${field.name} are invalid: ${validationErrors.map(e => e.error).join(', ')}`);
                }
                transformedDto = await fieldManager.transformForCreate(createDto);
            }
            const entity = this.repo.create(transformedDto);
            entitiesForSave.push(entity);
        }
        const savedEntities = await this.repo.save(entitiesForSave);
        return savedEntities;
    }
    async insertMany(createDtos, filesArray = [], solidRequestContext = {}) {
        const savedEntities = await this.createMany(createDtos, solidRequestContext);
        return savedEntities;
    }
    async deleteMany(ids, solidRequestContext = {}) {
        if (!ids || ids.length === 0) {
            throw new Error(error_messages_1.ERROR_MESSAGES.DELETE_IDS_REQUIRED);
        }
        const loadedmodel = await this.loadModel();
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasDeletePermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const model = await this.modelMetadataService.findOneBySingularName(this.modelName, {
            fields: {
                model: true,
                mediaStorageProvider: true,
            },
            module: true,
        });
        const removedEntities = [];
        for (let i = 0; i < ids.length; i++) {
            const id = ids[i];
            const entity = await this.repo.findOne({
                where: {
                    id: id,
                },
            });
            removedEntities.push(entity);
        }
        const isDraftPublishEnabled = model?.draftPublishWorkflow === true;
        let publishedEntitiesExists = [];
        if (isDraftPublishEnabled) {
            publishedEntitiesExists = removedEntities.filter((x) => !!x?.publishedAt);
        }
        if (publishedEntitiesExists.length > 0) {
            const publishedEntitiesExistsID = publishedEntitiesExists.map(x => x.id);
            throw new common_1.BadRequestException(`Cannot delete published record(s) for model ${this.modelName} with Ids ${publishedEntitiesExistsID.join(', ')}. Unpublish them first.`);
        }
        if (model.enableSoftDelete === true) {
            await this.repo.softRemove(removedEntities);
            return this.repo.save(removedEntities);
        }
        else {
            return this.repo.remove(removedEntities);
        }
    }
    async recover(id, solidRequestContext = {}) {
        try {
            const loadedmodel = await this.loadModel();
            if (solidRequestContext.activeUser) {
                const hasPermission = this.crudHelperService.hasRecoverPermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
                if (!hasPermission) {
                    throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
                }
            }
            const softDeletedRows = await this.repo.findOne({
                where: {
                    id, deletedAt: (0, typeorm_1.Not)((0, typeorm_1.IsNull)())
                },
                withDeleted: true,
            });
            if (!softDeletedRows) {
                throw new Error(error_messages_1.ERROR_MESSAGES.NO_SOFT_DELETED_RECORD_FOUND);
            }
            await this.repo.update(id, {
                deletedAt: null, deletedTracker: "not-deleted"
            });
            return { message: success_messages_1.SUCCESS_MESSAGES.RECORD_RECOVERED, data: softDeletedRows };
        }
        catch (error) {
            if (error instanceof typeorm_1.QueryFailedError) {
                if (error.code === '23505') {
                    throw new Error(error_messages_1.ERROR_MESSAGES.CONFLICTING_RECORD_ON_UNARCHIVE);
                }
            }
            throw new Error(error);
        }
    }
    async recoverMany(ids, solidRequestContext = {}) {
        try {
            const loadedmodel = await this.loadModel();
            if (solidRequestContext.activeUser) {
                const hasPermission = this.crudHelperService.hasRecoverPermissionOnModel(solidRequestContext.activeUser, loadedmodel.singularName);
                if (!hasPermission) {
                    throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
                }
            }
            if (!ids || ids.length === 0) {
                throw new Error(error_messages_1.ERROR_MESSAGES.DELETE_IDS_REQUIRED);
            }
            const softDeletedRows = await this.repo.find({
                where: {
                    id: (0, typeorm_1.In)(ids),
                    deletedAt: (0, typeorm_1.Not)((0, typeorm_1.IsNull)()),
                },
                withDeleted: true,
            });
            if (softDeletedRows.length === 0) {
                throw new Error(error_messages_1.ERROR_MESSAGES.NO_SOFT_DELETED_RECORDS_FOUND);
            }
            await this.repo.update({ id: (0, typeorm_1.In)(ids) }, { deletedAt: null, deletedTracker: "not-deleted" });
            return { message: success_messages_1.SUCCESS_MESSAGES.SELECTED_RECORDS_RECOVERED, recoveredIds: ids };
        }
        catch (error) {
            if (error instanceof typeorm_1.QueryFailedError) {
                if (error.code === "23505") {
                    throw new Error(error_messages_1.ERROR_MESSAGES.CONFLICTING_RECORD_ON_UNARCHIVE);
                }
            }
            throw new Error(error);
        }
    }
    async getFieldMetadataRecursively(pathParts, fields) {
        if (!pathParts || pathParts.length === 0) {
            throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.EMPTY_PATH_PARTS);
        }
        const [currentPart, ...remainingParts] = pathParts;
        const field = fields.find(field => field.name === currentPart);
        if (!field) {
            throw new common_1.BadRequestException(`Field ${currentPart} not found in model ${this.modelName}`);
        }
        if (remainingParts.length === 0) {
            return field;
        }
        if (!field.relationCoModelSingularName) {
            throw new common_1.BadRequestException(`Field ${field.name} does not define a relationCoModelSingularName`);
        }
        const relationCoModel = await this.getDatasourceDefaultEntityManager().getRepository(model_metadata_entity_1.ModelMetadata).findOne({
            where: { singularName: field.relationCoModelSingularName },
            relations: ['fields', 'fields.mediaStorageProvider', 'fields.model'],
        });
        if (!relationCoModel) {
            throw new common_1.BadRequestException(`Model ${field.relationCoModelSingularName} not found`);
        }
        return this.getFieldMetadataRecursively(remainingParts, relationCoModel.fields);
    }
    async getUserKeyFieldNameForModel(modelSingularName) {
        const model = await this.modelMetadataService.findOneBySingularName(modelSingularName, ['userKeyField']);
        if (!model) {
            throw new common_1.BadRequestException(`Model ${modelSingularName} not found`);
        }
        return model.userKeyField?.name || '';
    }
    async publishRecord(id, solidRequestContext = {}) {
        const model = await this.loadModel();
        if (!model.draftPublishWorkflow) {
            throw new common_1.BadRequestException(`Publish workflow is not enabled for ${this.modelName}`);
        }
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasPublishPermissionOnModel(solidRequestContext.activeUser, model.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const entity = await this.repo.findOne({ where: { id } });
        if (!entity) {
            throw new common_1.NotFoundException(`${this.modelName} with id ${id} not found`);
        }
        if (entity.publishedAt) {
            throw new common_1.BadRequestException(`${this.modelName} with id ${id} is already published`);
        }
        const updatedEntity = await this.repo.save({ ...entity, publishedAt: new Date() });
        return updatedEntity;
    }
    async unpublishRecord(id, solidRequestContext = {}) {
        const model = await this.loadModel();
        if (!model.draftPublishWorkflow) {
            throw new common_1.BadRequestException(`Publish workflow is not enabled for ${this.modelName}`);
        }
        if (solidRequestContext.activeUser) {
            const hasPermission = this.crudHelperService.hasUnpublishPermissionOnModel(solidRequestContext.activeUser, model.singularName);
            if (!hasPermission) {
                throw new common_1.BadRequestException(error_messages_1.ERROR_MESSAGES.FORBIDDEN);
            }
        }
        const entity = await this.repo.findOne({ where: { id } });
        if (!entity) {
            throw new common_1.NotFoundException(`${this.modelName} with id ${id} not found`);
        }
        if (!entity.publishedAt) {
            throw new common_1.BadRequestException(`${this.modelName} with id ${id} is already unpublished`);
        }
        const updatedEntity = await this.repo.save({ ...entity, publishedAt: null });
        return updatedEntity;
    }
    getDatasourceDefaultEntityManager() {
        return this.defaultDatasourceEntityManager ?? this.entityManager;
    }
}
exports.CRUDService = CRUDService;
//# sourceMappingURL=crud.service.js.map