Files
n8n-enterprise-unlocked/packages/editor-ui/src/stores/projects.store.ts

237 lines
7.3 KiB
TypeScript

import { defineStore } from 'pinia';
import { ref, watch, computed } from 'vue';
import { useRoute } from 'vue-router';
import { useRootStore } from '@/stores/root.store';
import * as projectsApi from '@/api/projects.api';
import * as workflowsEEApi from '@/api/workflows.ee';
import * as credentialsEEApi from '@/api/credentials.ee';
import type { Project, ProjectListItem, ProjectsCount } from '@/types/projects.types';
import { ProjectTypes } from '@/types/projects.types';
import { useSettingsStore } from '@/stores/settings.store';
import { hasPermission } from '@/utils/rbac/permissions';
import type { IWorkflowDb } from '@/Interface';
import { useCredentialsStore } from '@/stores/credentials.store';
import { STORES } from '@/constants';
import { useUsersStore } from '@/stores/users.store';
import { getResourcePermissions } from '@/permissions';
import type { CreateProjectDto, UpdateProjectDto } from '@n8n/api-types';
export const useProjectsStore = defineStore(STORES.PROJECTS, () => {
const route = useRoute();
const rootStore = useRootStore();
const settingsStore = useSettingsStore();
const credentialsStore = useCredentialsStore();
const usersStore = useUsersStore();
const projects = ref<ProjectListItem[]>([]);
const myProjects = ref<ProjectListItem[]>([]);
const personalProject = ref<Project | null>(null);
const currentProject = ref<Project | null>(null);
const projectsCount = ref<ProjectsCount>({
personal: 0,
team: 0,
public: 0,
});
const projectNavActiveIdState = ref<string | string[] | null>(null);
const globalProjectPermissions = computed(
() => getResourcePermissions(usersStore.currentUser?.globalScopes).project,
);
const availableProjects = computed(() =>
globalProjectPermissions.value.list ? projects.value : myProjects.value,
);
const currentProjectId = computed(
() =>
(route.params?.projectId as string | undefined) ??
(route.query?.projectId as string | undefined) ??
currentProject.value?.id,
);
const isProjectHome = computed(() => route.path.includes('home'));
const personalProjects = computed(() =>
projects.value.filter((p) => p.type === ProjectTypes.Personal),
);
const teamProjects = computed(() => projects.value.filter((p) => p.type === ProjectTypes.Team));
const teamProjectsLimit = computed(() => settingsStore.settings.enterprise.projects.team.limit);
const isTeamProjectFeatureEnabled = computed<boolean>(() => teamProjectsLimit.value !== 0);
const hasUnlimitedProjects = computed<boolean>(() => teamProjectsLimit.value === -1);
const isTeamProjectLimitExceeded = computed<boolean>(
() => projectsCount.value.team >= teamProjectsLimit.value,
);
const canCreateProjects = computed<boolean>(
() =>
hasUnlimitedProjects.value ||
(isTeamProjectFeatureEnabled.value && !isTeamProjectLimitExceeded.value),
);
const hasPermissionToCreateProjects = computed(() =>
hasPermission(['rbac'], { rbac: { scope: 'project:create' } }),
);
const projectNavActiveId = computed<string | string[] | null>({
get: () => route?.params?.projectId ?? projectNavActiveIdState.value,
set: (value: string | string[] | null) => {
projectNavActiveIdState.value = value;
},
});
const setCurrentProject = (project: Project | null) => {
currentProject.value = project;
};
const getAllProjects = async () => {
projects.value = await projectsApi.getAllProjects(rootStore.restApiContext);
};
const getMyProjects = async () => {
myProjects.value = await projectsApi.getMyProjects(rootStore.restApiContext);
};
const getPersonalProject = async () => {
personalProject.value = await projectsApi.getPersonalProject(rootStore.restApiContext);
};
const getAvailableProjects = async () => {
if (globalProjectPermissions.value.list) {
await getAllProjects();
} else {
await getMyProjects();
}
};
const fetchProject = async (id: string) =>
await projectsApi.getProject(rootStore.restApiContext, id);
const getProject = async (id: string) => {
currentProject.value = await fetchProject(id);
};
const createProject = async (project: CreateProjectDto): Promise<Project> => {
const newProject = await projectsApi.createProject(rootStore.restApiContext, project);
await getProjectsCount();
myProjects.value = [...myProjects.value, newProject as unknown as ProjectListItem];
return newProject;
};
const updateProject = async (
id: Project['id'],
projectData: Required<UpdateProjectDto>,
): Promise<void> => {
await projectsApi.updateProject(rootStore.restApiContext, id, projectData);
const projectIndex = myProjects.value.findIndex((p) => p.id === id);
const { name, icon } = projectData;
if (projectIndex !== -1) {
myProjects.value[projectIndex].name = name;
myProjects.value[projectIndex].icon = icon;
}
if (currentProject.value) {
currentProject.value.name = name;
currentProject.value.icon = icon;
}
if (projectData.relations) {
await getProject(id);
}
};
const deleteProject = async (projectId: string, transferId?: string): Promise<void> => {
await projectsApi.deleteProject(rootStore.restApiContext, projectId, transferId);
await getProjectsCount();
myProjects.value = myProjects.value.filter((p) => p.id !== projectId);
};
const getProjectsCount = async () => {
projectsCount.value = await projectsApi.getProjectsCount(rootStore.restApiContext);
};
const setProjectNavActiveIdByWorkflowHomeProject = async (
homeProject?: IWorkflowDb['homeProject'],
) => {
if (homeProject?.type === ProjectTypes.Personal) {
projectNavActiveId.value = 'home';
} else {
projectNavActiveId.value = homeProject?.id ?? null;
if (homeProject?.id && !currentProjectId.value) {
await getProject(homeProject?.id);
}
}
};
const moveResourceToProject = async (
resourceType: 'workflow' | 'credential',
resourceId: string,
projectId: string,
shareCredentials?: string[],
) => {
if (resourceType === 'workflow') {
await workflowsEEApi.moveWorkflowToProject(rootStore.restApiContext, resourceId, {
destinationProjectId: projectId,
shareCredentials,
});
} else {
await credentialsEEApi.moveCredentialToProject(
rootStore.restApiContext,
resourceId,
projectId,
);
await credentialsStore.fetchAllCredentials(currentProjectId.value);
}
};
watch(
route,
async (newRoute) => {
projectNavActiveId.value = null;
if (newRoute?.path?.includes('home')) {
projectNavActiveId.value = 'home';
setCurrentProject(null);
}
if (newRoute?.path?.includes('workflow/')) {
if (currentProjectId.value) {
projectNavActiveId.value = currentProjectId.value;
} else {
projectNavActiveId.value = 'home';
}
}
if (!newRoute?.params?.projectId) {
return;
}
await getProject(newRoute.params.projectId as string);
},
{ immediate: true },
);
return {
projects,
availableProjects,
myProjects,
personalProject,
currentProject,
currentProjectId,
isProjectHome,
personalProjects,
teamProjects,
teamProjectsLimit,
hasUnlimitedProjects,
canCreateProjects,
hasPermissionToCreateProjects,
isTeamProjectFeatureEnabled,
projectNavActiveId,
setCurrentProject,
getAllProjects,
getMyProjects,
getPersonalProject,
getAvailableProjects,
fetchProject,
getProject,
createProject,
updateProject,
deleteProject,
getProjectsCount,
setProjectNavActiveIdByWorkflowHomeProject,
moveResourceToProject,
};
});