"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 __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;
    };
})();
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectService = exports.UnlicensedProjectRoleError = exports.TeamProjectOverQuotaError = void 0;
const backend_common_1 = require("@n8n/backend-common");
const constants_1 = require("@n8n/constants");
const db_1 = require("@n8n/db");
const di_1 = require("@n8n/di");
const permissions_1 = require("@n8n/permissions");
const typeorm_1 = require("@n8n/typeorm");
const n8n_workflow_1 = require("n8n-workflow");
const bad_request_error_1 = require("../errors/response-errors/bad-request.error");
const forbidden_error_1 = require("../errors/response-errors/forbidden.error");
const not_found_error_1 = require("../errors/response-errors/not-found.error");
const role_service_1 = require("./role.service");
class TeamProjectOverQuotaError extends n8n_workflow_1.UserError {
    constructor(limit) {
        super(`Attempted to create a new project but quota is already exhausted. You may have a maximum of ${limit} team projects.`);
    }
}
exports.TeamProjectOverQuotaError = TeamProjectOverQuotaError;
class UnlicensedProjectRoleError extends n8n_workflow_1.UserError {
    constructor(role) {
        super(`Your instance is not licensed to use role "${role}".`);
    }
}
exports.UnlicensedProjectRoleError = UnlicensedProjectRoleError;
class ProjectNotFoundError extends not_found_error_1.NotFoundError {
    constructor(projectId) {
        super(`Could not find project with ID: ${projectId}`);
    }
    static isDefinedAndNotNull(value, projectId) {
        if (value === undefined || value === null) {
            throw new ProjectNotFoundError(projectId);
        }
    }
}
let ProjectService = class ProjectService {
    constructor(sharedWorkflowRepository, projectRepository, projectRelationRepository, roleService, sharedCredentialsRepository, licenseState, moduleRegistry) {
        this.sharedWorkflowRepository = sharedWorkflowRepository;
        this.projectRepository = projectRepository;
        this.projectRelationRepository = projectRelationRepository;
        this.roleService = roleService;
        this.sharedCredentialsRepository = sharedCredentialsRepository;
        this.licenseState = licenseState;
        this.moduleRegistry = moduleRegistry;
    }
    get workflowService() {
        return Promise.resolve().then(() => __importStar(require('../workflows/workflow.service'))).then(({ WorkflowService }) => di_1.Container.get(WorkflowService));
    }
    get credentialsService() {
        return Promise.resolve().then(() => __importStar(require('../credentials/credentials.service'))).then(({ CredentialsService }) => di_1.Container.get(CredentialsService));
    }
    get folderService() {
        return Promise.resolve().then(() => __importStar(require('../services/folder.service'))).then(({ FolderService }) => di_1.Container.get(FolderService));
    }
    get dataTableService() {
        return Promise.resolve().then(() => __importStar(require('../modules/data-table/data-table.service'))).then(({ DataTableService }) => di_1.Container.get(DataTableService));
    }
    get secretsProvidersConnectionsService() {
        return Promise.resolve().then(() => __importStar(require('../modules/external-secrets.ee/secrets-providers-connections.service.ee'))).then(({ SecretsProvidersConnectionsService }) => di_1.Container.get(SecretsProvidersConnectionsService));
    }
    async deleteProject(user, projectId, { migrateToProject } = {}) {
        const workflowService = await this.workflowService;
        const credentialsService = await this.credentialsService;
        if (projectId === migrateToProject) {
            throw new bad_request_error_1.BadRequestError('Request to delete a project failed because the project to delete and the project to migrate to are the same project');
        }
        const project = await this.getProjectWithScope(user, projectId, ['project:delete']);
        ProjectNotFoundError.isDefinedAndNotNull(project, projectId);
        let targetProject = null;
        if (migrateToProject) {
            targetProject = await this.getProjectWithScope(user, migrateToProject, [
                'credential:create',
                'workflow:create',
                'dataTable:create',
            ]);
            if (!targetProject) {
                throw new not_found_error_1.NotFoundError(`Could not find project to migrate to. ID: ${targetProject}. You may lack permissions to create workflow, credentials or data tables in the target project.`);
            }
        }
        if (project.type !== 'team') {
            throw new forbidden_error_1.ForbiddenError(`Can't delete project. Project with ID "${projectId}" is not a team project.`);
        }
        const ownedSharedWorkflows = await this.sharedWorkflowRepository.find({
            where: { projectId: project.id, role: 'workflow:owner' },
        });
        if (targetProject) {
            await this.sharedWorkflowRepository.makeOwner(ownedSharedWorkflows.map((sw) => sw.workflowId), targetProject.id);
        }
        else {
            for (const sharedWorkflow of ownedSharedWorkflows) {
                await workflowService.delete(user, sharedWorkflow.workflowId, true);
            }
        }
        const ownedCredentials = await this.sharedCredentialsRepository.find({
            where: { projectId: project.id, role: 'credential:owner' },
            relations: { credentials: true },
        });
        if (targetProject) {
            await this.sharedCredentialsRepository.makeOwner(ownedCredentials.map((sc) => sc.credentialsId), targetProject.id);
        }
        else {
            for (const sharedCredential of ownedCredentials) {
                await credentialsService.delete(user, sharedCredential.credentials.id);
            }
        }
        if (targetProject) {
            const folderService = await this.folderService;
            await folderService.transferAllFoldersToProject(project.id, targetProject.id);
        }
        if (this.moduleRegistry.isActive('data-table')) {
            const dataTableService = await this.dataTableService;
            if (targetProject) {
                await dataTableService.transferDataTablesByProjectId(project.id, targetProject.id);
            }
            else {
                await dataTableService.deleteDataTableByProjectId(project.id);
            }
        }
        if (this.moduleRegistry.isActive('external-secrets')) {
            const secretsProvidersConnectionsService = await this.secretsProvidersConnectionsService;
            await secretsProvidersConnectionsService.cleanupConnectionsForProjectDeletion(project.id);
        }
        await this.projectRepository.remove(project);
    }
    async findProjectsWorkflowIsIn(workflowId) {
        return await this.sharedWorkflowRepository.findProjectIds(workflowId);
    }
    async addUserScopes(user, projects) {
        if (projects.length === 0)
            return [];
        const relations = await this.projectRelationRepository.find({
            where: {
                userId: user.id,
                projectId: (0, typeorm_1.In)(projects.map((p) => p.id)),
            },
            relations: ['role'],
        });
        const relationsByProject = new Map(relations.map((r) => [r.projectId, r]));
        const globalScopes = (0, permissions_1.getAuthPrincipalScopes)(user);
        return projects.map((project) => {
            const relation = relationsByProject.get(project.id);
            const projectScopes = relation?.role?.scopes?.map((s) => s.slug) ?? [];
            return {
                ...project,
                role: relation?.role?.slug ?? user.role.slug,
                scopes: [
                    ...new Set((0, permissions_1.combineScopes)({
                        global: globalScopes,
                        ...(projectScopes.length ? { project: projectScopes } : {}),
                    })),
                ].sort(),
            };
        });
    }
    async getAccessibleProjects(user) {
        if ((0, permissions_1.hasGlobalScope)(user, 'project:read')) {
            return await this.projectRepository.find();
        }
        return await this.projectRepository.getAccessibleProjects(user.id);
    }
    async getAccessibleProjectsAndCount(user, options) {
        if ((0, permissions_1.hasGlobalScope)(user, 'project:read')) {
            return await this.projectRepository.findAllProjectsAndCount(options);
        }
        return await this.projectRepository.getAccessibleProjectsAndCount(user.id, options);
    }
    async getPersonalProjectOwners(projectIds) {
        return await this.projectRelationRepository.getPersonalProjectOwners(projectIds);
    }
    async createTeamProjectWithEntityManager(adminUser, data, trx) {
        const limit = this.licenseState.getMaxTeamProjects();
        if (limit !== constants_1.UNLIMITED_LICENSE_QUOTA) {
            const teamProjectCount = await trx.count(db_1.Project, { where: { type: 'team' } });
            if (teamProjectCount >= limit) {
                throw new TeamProjectOverQuotaError(limit);
            }
        }
        const project = await trx.save(db_1.Project, this.projectRepository.create({ ...data, type: 'team', creatorId: adminUser.id }));
        await this.addUser(project.id, { userId: adminUser.id, role: 'project:admin' }, trx);
        return project;
    }
    async createTeamProject(adminUser, data) {
        return await this.projectRepository.manager.transaction('SERIALIZABLE', async (trx) => {
            return await this.createTeamProjectWithEntityManager(adminUser, data, trx);
        });
    }
    async updateProject(projectId, { name, icon, description }) {
        const result = await this.projectRepository.update({ id: projectId, type: 'team' }, { name, icon, description });
        if (!result.affected) {
            throw new ProjectNotFoundError(projectId);
        }
    }
    async getPersonalProject(user) {
        return await this.projectRepository.getPersonalProjectForUser(user.id);
    }
    async getProjectRelationsForUser(user) {
        return await this.projectRelationRepository.find({
            where: { userId: user.id },
            relations: ['project', 'role'],
        });
    }
    async syncProjectRelations(projectId, relations) {
        const project = await this.getTeamProjectWithRelations(projectId);
        this.checkRolesLicensed(project, relations);
        await this.roleService.checkRolesExist(relations.map((r) => r.role), 'project');
        await this.projectRelationRepository.manager.transaction(async (em) => {
            await this.pruneRelations(em, project);
            await this.addManyRelations(em, project, relations);
        });
        const newRelations = relations.filter((relation) => !project.projectRelations.some((r) => r.userId === relation.userId));
        return { project, newRelations };
    }
    async addUsersToProject(projectId, relations) {
        const project = await this.getTeamProjectWithRelations(projectId);
        this.checkRolesLicensed(project, relations);
        await this.roleService.checkRolesExist(relations.map((r) => r.role), 'project');
        if (project.type === 'personal') {
            throw new forbidden_error_1.ForbiddenError("Can't add users to personal projects.");
        }
        if (relations.some((r) => r.role === permissions_1.PROJECT_OWNER_ROLE_SLUG)) {
            throw new forbidden_error_1.ForbiddenError("Can't add a personalOwner to a team project.");
        }
        await this.projectRelationRepository.save(relations.map((relation) => ({
            projectId,
            userId: relation.userId,
            role: { slug: relation.role },
        })));
    }
    async addUsersWithConflictSemantics(projectId, relations) {
        const project = await this.getTeamProjectWithRelations(projectId);
        this.checkRolesLicensed(project, relations);
        await this.roleService.checkRolesExist(relations.map((r) => r.role), 'project');
        const existingByUserId = new Map(project.projectRelations.map((r) => [r.userId, r]));
        const added = [];
        const conflicts = [];
        for (const rel of relations) {
            const existing = existingByUserId.get(rel.userId);
            if (!existing)
                continue;
            const current = existing.role?.slug;
            if (current && current !== rel.role && (0, permissions_1.isAssignableProjectRoleSlug)(current)) {
                conflicts.push({ userId: rel.userId, currentRole: current, requestedRole: rel.role });
            }
        }
        const toInsert = relations.filter((rel) => !existingByUserId.has(rel.userId));
        if (toInsert.length > 0) {
            await this.projectRelationRepository.insert(toInsert.map((v) => ({
                projectId: project.id,
                userId: v.userId,
                role: { slug: v.role },
            })));
            added.push(...toInsert);
        }
        return { project, added, conflicts };
    }
    async getTeamProjectWithRelations(projectId) {
        const project = await this.projectRepository.findOne({
            where: { id: projectId, type: 'team' },
            relations: { projectRelations: { role: true } },
        });
        ProjectNotFoundError.isDefinedAndNotNull(project, projectId);
        return project;
    }
    checkRolesLicensed(project, relations) {
        for (const { role, userId } of relations) {
            const existing = project.projectRelations.find((pr) => pr.userId === userId);
            if (existing?.role?.slug !== role && !this.roleService.isRoleLicensed(role)) {
                throw new UnlicensedProjectRoleError(role);
            }
        }
    }
    isUserProjectOwner(project, userId) {
        return project.projectRelations.some((pr) => pr.userId === userId && pr.role.slug === permissions_1.PROJECT_OWNER_ROLE_SLUG);
    }
    async deleteUserFromProject(projectId, userId) {
        const project = await this.getTeamProjectWithRelations(projectId);
        if (this.isUserProjectOwner(project, userId)) {
            throw new forbidden_error_1.ForbiddenError('Project owner cannot be removed from the project');
        }
        await this.projectRelationRepository.delete({ projectId: project.id, userId });
    }
    async changeUserRoleInProject(projectId, userId, role) {
        if (role === permissions_1.PROJECT_OWNER_ROLE_SLUG) {
            throw new forbidden_error_1.ForbiddenError('Personal owner cannot be added to a team project.');
        }
        const project = await this.getTeamProjectWithRelations(projectId);
        await this.roleService.checkRolesExist([role], 'project');
        ProjectNotFoundError.isDefinedAndNotNull(project, projectId);
        const projectUserExists = project.projectRelations.some((r) => r.userId === userId);
        if (!projectUserExists) {
            throw new ProjectNotFoundError(projectId);
        }
        const currentRelation = project.projectRelations.find((r) => r.userId === userId);
        const currentRole = currentRelation?.role?.slug;
        if (currentRole !== role && !this.roleService.isRoleLicensed(role)) {
            throw new UnlicensedProjectRoleError(role);
        }
        await this.projectRelationRepository.update({ projectId, userId }, { role: { slug: role } });
    }
    async pruneRelations(em, project) {
        await em.delete(db_1.ProjectRelation, { projectId: project.id });
    }
    async addManyRelations(em, project, relations) {
        await em.insert(db_1.ProjectRelation, relations.map((v) => this.projectRelationRepository.create({
            projectId: project.id,
            userId: v.userId,
            role: { slug: v.role },
        })));
    }
    async getProjectWithScope(user, projectId, scopes, entityManager) {
        const em = entityManager ?? this.projectRepository.manager;
        let where = {
            id: projectId,
        };
        if (!(0, permissions_1.hasGlobalScope)(user, scopes, { mode: 'allOf' })) {
            const projectRoles = await this.roleService.rolesWithScope('project', scopes, em);
            where = {
                ...where,
                projectRelations: {
                    role: (0, typeorm_1.In)(projectRoles),
                    userId: user.id,
                },
            };
        }
        return await em.findOne(db_1.Project, {
            where,
        });
    }
    async getProjectIdsWithScope(user, scopes, projectIds) {
        const where = {};
        if (projectIds) {
            where.id = (0, typeorm_1.In)(projectIds);
        }
        if (!(0, permissions_1.hasGlobalScope)(user, scopes, { mode: 'allOf' })) {
            const projectRoles = await this.roleService.rolesWithScope('project', scopes);
            where.type = 'team';
            where.projectRelations = {
                role: (0, typeorm_1.In)(projectRoles),
                userId: user.id,
            };
        }
        const projects = await this.projectRepository.find({
            where,
            select: ['id'],
        });
        return projects.map((p) => p.id);
    }
    async addUser(projectId, { userId, role }, trx) {
        trx = trx ?? this.projectRelationRepository.manager;
        return await trx.save(db_1.ProjectRelation, {
            projectId,
            userId,
            role: { slug: role },
        });
    }
    async getProject(projectId) {
        return await this.projectRepository.findOneOrFail({
            where: {
                id: projectId,
            },
        });
    }
    async getProjectRelations(projectId) {
        return await this.projectRelationRepository.find({
            where: { projectId },
            relations: { user: true, role: true },
        });
    }
    async getProjectRelationForUserAndProject(userId, projectId) {
        return await this.projectRelationRepository.findOne({
            where: { projectId, userId },
            relations: { user: true, role: true },
        });
    }
    async getUserOwnedOrAdminProjects(userId) {
        return await this.projectRepository.find({
            where: {
                projectRelations: {
                    userId,
                    role: (0, typeorm_1.In)([permissions_1.PROJECT_OWNER_ROLE_SLUG, permissions_1.PROJECT_ADMIN_ROLE_SLUG]),
                },
            },
        });
    }
    async getProjectCounts() {
        return await this.projectRepository.getProjectCounts();
    }
};
exports.ProjectService = ProjectService;
exports.ProjectService = ProjectService = __decorate([
    (0, di_1.Service)(),
    __metadata("design:paramtypes", [db_1.SharedWorkflowRepository,
        db_1.ProjectRepository,
        db_1.ProjectRelationRepository,
        role_service_1.RoleService,
        db_1.SharedCredentialsRepository,
        backend_common_1.LicenseState,
        backend_common_1.ModuleRegistry])
], ProjectService);
//# sourceMappingURL=project.service.ee.js.map