[Apps] Apps marketplace (first draft) (#17562)

https://github.com/user-attachments/assets/c4e63edb-6e98-44bc-841f-ee110ae712d4

How it works
- `manifest.json` files are now committed when apps are published to our
repo
- to display available apps, from the server, we read into our github
repo, using a pod-scoped cache
- feature flagged
- app installation will be behind permission gate MARKETPLACE_APPS

Limitations and what is yet to develop
- content and settings tabs
- installed apps tab
- app installation
- test and potentially fix reading from .manifest.json once [Reshape
manifest
structure](https://github.com/twentyhq/core-team-issues/issues/2183) is
done. additional work is expected on assets notably. (couldnt properly
do it here as manifest.json will only be committed after this pr)
- we only read in community/ folder for now - we may want tochange that
- the cache is rather artisanal for now and scoped by pod - we may want
to change that
This commit is contained in:
Marie
2026-01-30 18:32:32 +01:00
committed by GitHub
parent 36cf388c81
commit 0001c2e7d0
98 changed files with 1713 additions and 280 deletions
@@ -1,22 +1,23 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { ApplicationResolver } from 'src/engine/core-modules/application/application.resolver';
import { ApplicationResolver } from 'src/engine/core-modules/application/resolvers/application.resolver';
import { MarketplaceResolver } from 'src/engine/core-modules/application/resolvers/marketplace.resolver';
import { ApplicationSyncService } from 'src/engine/core-modules/application/services/application-sync.service';
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module';
import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { RoleModule } from 'src/engine/metadata-modules/role/role.module';
import { LogicFunctionLayerModule } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.module';
import { LogicFunctionModule } from 'src/engine/metadata-modules/logic-function/logic-function.module';
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/workspace-migration-runner.module';
@@ -46,6 +47,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
],
providers: [
ApplicationResolver,
MarketplaceResolver,
ApplicationSyncService,
WorkspaceMigrationGraphqlApiExceptionInterceptor,
],
@@ -2,7 +2,8 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { MarketplaceService } from 'src/engine/core-modules/application/services/marketplace.service';
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentEntity } from 'src/engine/metadata-modules/ai/ai-agent/entities/agent.entity';
@@ -15,7 +16,15 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
WorkspaceManyOrAllFlatEntityMapsCacheModule,
WorkspaceCacheModule,
],
exports: [ApplicationService, WorkspaceFlatApplicationMapCacheService],
providers: [ApplicationService, WorkspaceFlatApplicationMapCacheService],
exports: [
ApplicationService,
WorkspaceFlatApplicationMapCacheService,
MarketplaceService,
],
providers: [
ApplicationService,
WorkspaceFlatApplicationMapCacheService,
MarketplaceService,
],
})
export class ApplicationModule {}
@@ -0,0 +1,70 @@
import { Field, ObjectType } from '@nestjs/graphql';
import {
IsArray,
IsNotEmpty,
IsOptional,
IsString,
MaxLength,
} from 'class-validator';
@ObjectType('MarketplaceApp')
export class MarketplaceAppDTO {
@IsString()
@IsNotEmpty()
@Field()
id: string;
@IsString()
@IsNotEmpty()
@Field()
name: string;
@IsString()
@Field()
@MaxLength(160)
description: string;
@IsString()
@Field()
icon: string;
@IsString()
@Field()
version: string;
@IsString()
@Field()
author: string;
@IsString()
@Field()
category: string;
@IsOptional()
@IsString()
@Field({ nullable: true })
logo?: string;
@IsArray()
@Field(() => [String])
screenshots: string[];
@IsString()
@Field()
aboutDescription: string;
@IsArray()
@Field(() => [String])
providers: string[];
@IsOptional()
@IsString()
@Field({ nullable: true })
websiteUrl?: string;
@IsOptional()
@IsString()
@Field({ nullable: true })
termsUrl?: string;
}
@@ -16,17 +16,17 @@ import type { FileUpload } from 'graphql-upload/processRequest.mjs';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ApplicationExceptionFilter } from 'src/engine/core-modules/application/application-exception-filter';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
import { InstallApplicationInput } from 'src/engine/core-modules/application/dtos/install-application.input';
import { UninstallApplicationInput } from 'src/engine/core-modules/application/dtos/uninstallApplicationInput';
import { UploadApplicationFileInput } from 'src/engine/core-modules/application/dtos/uploadApplicationFileInput';
import { ApplicationSyncService } from 'src/engine/core-modules/application/services/application-sync.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { FileDTO } from 'src/engine/core-modules/file/dtos/file.dto';
@@ -0,0 +1,29 @@
import { UseGuards } from '@nestjs/common';
import { Mutation, Query, Resolver } from '@nestjs/graphql';
import { PermissionFlagType } from 'twenty-shared/constants';
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/dtos/marketplace-app.dto';
import { MarketplaceService } from 'src/engine/core-modules/application/services/marketplace.service';
import { NoPermissionGuard } from 'src/engine/guards/no-permission.guard';
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
import { UserAuthGuard } from 'src/engine/guards/user-auth.guard';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@Resolver()
@UseGuards(UserAuthGuard, WorkspaceAuthGuard, NoPermissionGuard)
export class MarketplaceResolver {
constructor(private readonly marketplaceService: MarketplaceService) {}
@Query(() => [MarketplaceAppDTO])
async findManyMarketplaceApps(): Promise<MarketplaceAppDTO[]> {
return this.marketplaceService.findAllMarketplaceApps();
}
@Mutation(() => Boolean)
@UseGuards(SettingsPermissionGuard(PermissionFlagType.MARKETPLACE_APPS))
async installMarketplaceApp(): Promise<boolean> {
// TODO
return true;
}
}
@@ -5,10 +5,10 @@ import { parse } from 'path';
import {
Manifest,
FieldManifest,
ObjectManifest,
RoleManifest,
LogicFunctionManifest,
LogicFunctionTriggerManifest,
ObjectManifest,
RoleManifest,
ObjectFieldManifest,
RelationFieldManifest,
} from 'twenty-shared/application';
@@ -20,8 +20,8 @@ import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationInput } from 'src/engine/core-modules/application/dtos/application.input';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ApplicationVariableEntityService } from 'src/engine/core-modules/applicationVariable/application-variable.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
@@ -35,15 +35,15 @@ import { ObjectPermissionService } from 'src/engine/metadata-modules/object-perm
import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service';
import { RoleService } from 'src/engine/metadata-modules/role/role.service';
import { LogicFunctionLayerService } from 'src/engine/metadata-modules/logic-function-layer/logic-function-layer.service';
import { LogicFunctionV2Service } from 'src/engine/metadata-modules/logic-function/services/logic-function-v2.service';
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
import {
CronTriggerSettings,
DatabaseEventTriggerSettings,
HttpRouteTriggerSettings,
} from 'src/engine/metadata-modules/logic-function/logic-function.entity';
import { LogicFunctionV2Service } from 'src/engine/metadata-modules/logic-function/services/logic-function-v2.service';
import { FlatLogicFunction } from 'src/engine/metadata-modules/logic-function/types/flat-logic-function.type';
import { computeMetadataNameFromLabelOrThrow } from 'src/engine/metadata-modules/utils/compute-metadata-name-from-label-or-throw.util';
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
@Injectable()
export class ApplicationSyncService {
@@ -0,0 +1,250 @@
import { Injectable, Logger } from '@nestjs/common';
// eslint-disable-next-line no-restricted-imports
import { lowerCase, upperFirst } from 'lodash';
import { type Manifest } from 'twenty-shared/application';
import { MarketplaceAppDTO } from 'src/engine/core-modules/application/dtos/marketplace-app.dto';
type GitHubContent = {
name: string;
path: string;
type: 'file' | 'dir';
download_url?: string;
};
const GITHUB_RAW_BASE_URL =
'https://raw.githubusercontent.com/twentyhq/twenty/main/packages/twenty-apps';
const GITHUB_API_BASE_URL =
'https://api.github.com/repos/twentyhq/twenty/contents/packages/twenty-apps';
const CACHE_TTL_MS = 60 * 60 * 1000;
@Injectable()
export class MarketplaceService {
private readonly logger = new Logger(MarketplaceService.name);
private cachedApps: MarketplaceAppDTO[] | null = null;
private cacheTimestamp: number | null = null;
async findAllMarketplaceApps(): Promise<MarketplaceAppDTO[]> {
if (this.isCacheValid()) {
return this.cachedApps as MarketplaceAppDTO[];
}
const apps = await this.fetchAllMarketplaceApps();
this.cachedApps = apps;
this.cacheTimestamp = Date.now();
return apps;
}
private isCacheValid(): boolean {
if (this.cachedApps === null || this.cacheTimestamp === null) {
return false;
}
return Date.now() - this.cacheTimestamp < CACHE_TTL_MS;
}
private async fetchAllMarketplaceApps(): Promise<MarketplaceAppDTO[]> {
const apps: MarketplaceAppDTO[] = [];
apps.push(this.loadMockApp());
try {
const appDirs = await this.getAppDirectoriesFromGitHub();
for (const appDir of appDirs) {
try {
const manifest = await this.loadAppManifestFromGithub(appDir);
if (manifest) {
apps.push(manifest);
} else {
// to remove after manifest are committed to the repo
const appName = appDir.replace(/^community\//, '');
const appLabel = upperFirst(lowerCase(appName));
apps.push({
id: appName,
name: appLabel,
description: '',
icon: '',
version: '',
author: 'Anonymous',
category: '',
logo: undefined,
screenshots: [],
aboutDescription: '',
providers: [],
websiteUrl: '',
termsUrl: '',
});
}
} catch (error) {
this.logger.warn(
`Failed to load manifest from ${appDir}: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
}
}
} catch (error) {
this.logger.error(
`Failed to fetch marketplace apps from GitHub: ${error instanceof Error ? error.message : 'Unknown error'}`,
);
}
return apps;
}
private loadMockApp(): MarketplaceAppDTO {
// SVG logo as data URL
const logoSvg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#1a2744"><ellipse cx="38" cy="20" rx="28" ry="10"/><rect x="10" y="20" width="56" height="50"/><ellipse cx="38" cy="70" rx="28" ry="10"/><ellipse cx="38" cy="35" rx="28" ry="10" fill="none" stroke="#fff" stroke-width="3"/><ellipse cx="38" cy="52" rx="28" ry="10" fill="none" stroke="#fff" stroke-width="3"/><circle cx="72" cy="62" r="22" fill="#1a2744"/><circle cx="72" cy="62" r="18" fill="#fff"/><path d="M72 50 L72 74 M62 58 L72 48 L82 58" stroke="#1a2744" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/></svg>`;
const logoDataUrl = `data:image/svg+xml,${encodeURIComponent(logoSvg)}`;
return {
id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
name: 'Data Enrichment',
description: 'Enrich your data easily. Choose your provider.',
icon: 'IconSparkles',
version: '1.0.0',
author: 'Cosmos Labs',
category: 'Data',
logo: logoDataUrl,
screenshots: [
'https://placehold.co/800x400/f5f5f5/666?text=Screenshot+1',
'https://placehold.co/800x400/f5f5f5/666?text=Screenshot+2',
'https://placehold.co/800x400/f5f5f5/666?text=Screenshot+3',
],
aboutDescription:
'Enhance your workspace with automated data intelligence. This app monitors your new records and automatically populates missing details such as job titles, company size, social profiles, and industry insights.',
providers: ['Clearbit', 'Apollo', 'Hunter.io'],
websiteUrl: 'https://google.com',
termsUrl: 'https://google.com',
};
}
private async getAppDirectoriesFromGitHub(): Promise<string[]> {
const directories: string[] = [];
const rootContents = await this.fetchGitHubDirectory('');
for (const entry of rootContents) {
// if (entry.type !== 'dir') continue;
// if (entry.name.startsWith('.')) continue;
// if (entry.name === 'node_modules') continue;
// if (entry.name === 'internal') continue;
if (entry.name === 'community') {
const communityContents = await this.fetchGitHubDirectory('community');
for (const communityEntry of communityContents) {
if (communityEntry.type !== 'dir') continue;
if (communityEntry.name.startsWith('.')) continue;
directories.push(`community/${communityEntry.name}`);
}
}
}
return directories;
}
private async fetchGitHubDirectory(path: string): Promise<GitHubContent[]> {
const url = path ? `${GITHUB_API_BASE_URL}/${path}` : GITHUB_API_BASE_URL;
const response = await fetch(url, {
headers: {
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'Twenty-Marketplace',
},
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.status}`);
}
return response.json();
}
private async fetchGitHubFile(path: string): Promise<string | null> {
const url = `${GITHUB_RAW_BASE_URL}/${path}`;
const response = await fetch(url, {
headers: {
'User-Agent': 'Twenty-Marketplace',
},
});
if (!response.ok) {
if (response.status === 404) {
return null;
}
throw new Error(`GitHub raw file error: ${response.status}`);
}
return response.text();
}
private async loadAppManifestFromGithub(
appPath: string,
): Promise<MarketplaceAppDTO | null> {
const manifestContent = await this.fetchGitHubFile(
`${appPath}/.twenty/output/manifest.json`,
);
if (!manifestContent) {
return null;
}
const manifest = JSON.parse(manifestContent) as Manifest;
const { application, packageJson } = manifest;
const marketplaceData = application.marketplaceData;
if (!marketplaceData?.author || !marketplaceData?.category) {
return null;
}
return {
id: application.universalIdentifier,
name: application.displayName ?? packageJson.name,
description: application.description ?? '',
icon: application.icon ?? 'IconApps',
version: packageJson.version,
author: marketplaceData.author,
category: marketplaceData.category,
logo: this.resolveAssetUrl(appPath, marketplaceData.logo),
screenshots: this.resolveAssetUrls(appPath, marketplaceData.screenshots),
aboutDescription: marketplaceData.aboutDescription ?? '',
providers: marketplaceData.providers ?? [],
websiteUrl: marketplaceData.websiteUrl,
termsUrl: marketplaceData.termsUrl,
};
}
private resolveAssetUrl(
appPath: string,
relativePath: string | undefined,
): string | undefined {
if (!relativePath) {
return undefined;
}
return `${GITHUB_RAW_BASE_URL}/${appPath}/${relativePath}`;
}
private resolveAssetUrls(
appPath: string,
relativePaths: string[] | undefined,
): string[] {
if (!relativePaths) {
return [];
}
return relativePaths
.map((path) => this.resolveAssetUrl(appPath, path))
.filter((url): url is string => url !== undefined);
}
}
@@ -10,7 +10,7 @@ import { v4 } from 'uuid';
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import {
AuthException,
AuthExceptionCode,
@@ -4,6 +4,7 @@ export enum FeatureFlagKey {
IS_AI_ENABLED = 'IS_AI_ENABLED',
IS_APPLICATION_ENABLED = 'IS_APPLICATION_ENABLED',
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED = 'IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED',
IS_MARKETPLACE_ENABLED = 'IS_MARKETPLACE_ENABLED',
IS_RECORD_PAGE_LAYOUT_ENABLED = 'IS_RECORD_PAGE_LAYOUT_ENABLED',
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED = 'IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED',
IS_PUBLIC_DOMAIN_ENABLED = 'IS_PUBLIC_DOMAIN_ENABLED',
@@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="#1a2744">
<!-- Database cylinder -->
<ellipse cx="38" cy="20" rx="28" ry="10"/>
<rect x="10" y="20" width="56" height="50"/>
<ellipse cx="38" cy="70" rx="28" ry="10"/>
<!-- Database stripes -->
<ellipse cx="38" cy="35" rx="28" ry="10" fill="none" stroke="#fff" stroke-width="3"/>
<ellipse cx="38" cy="52" rx="28" ry="10" fill="none" stroke="#fff" stroke-width="3"/>
<!-- Upload circle -->
<circle cx="72" cy="62" r="22" fill="#1a2744"/>
<circle cx="72" cy="62" r="18" fill="#fff"/>
<!-- Upload arrow -->
<path d="M72 50 L72 74 M62 58 L72 48 L82 58" stroke="#1a2744" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
</svg>

After

Width:  |  Height:  |  Size: 743 B

@@ -0,0 +1,37 @@
{
"application": {
"universalIdentifier": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"functionRoleUniversalIdentifier": "00000000-0000-0000-0000-000000000000",
"displayName": "Data Enrichment",
"description": "Enrich your data easily. Choose your provider.",
"icon": "IconSparkles",
"marketplaceData": {
"author": "Cosmos Labs",
"category": "Data",
"logo": "assets/logo.png",
"screenshots": [
"assets/screenshot-1.png",
"assets/screenshot-2.png",
"assets/screenshot-3.png"
],
"aboutDescription": "Enhance your workspace with automated data intelligence. This app monitors your new records and automatically populates missing details such as job titles, company size, social profiles, and industry insights.",
"providers": ["Clearbit", "Apollo", "Hunter.io"],
"websiteUrl": "https://google.com",
"termsUrl": "https://google.com"
}
},
"entities": {
"objects": [],
"fields": [],
"logicFunctions": [],
"frontComponents": [],
"roles": []
},
"publicAssets": [],
"sources": {},
"packageJson": {
"name": "@twenty-apps/data-enrichment",
"version": "1.0.0"
},
"yarnLock": ""
}
@@ -4,7 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { type Repository, type UpdateResult } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { AuthException } from 'src/engine/core-modules/auth/auth.exception';
import { WorkspaceDomainsService } from 'src/engine/core-modules/domain/workspace-domains/services/workspace-domains.service';
import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service';
@@ -24,7 +24,7 @@ import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import type { FileUpload } from 'graphql-upload/processRequest.mjs';
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ApplicationDTO } from 'src/engine/core-modules/application/dtos/application.dto';
import { fromFlatApplicationToApplicationDto } from 'src/engine/core-modules/application/utils/from-flat-application-to-application-dto.util';
import { BillingEntitlementDTO } from 'src/engine/core-modules/billing/dtos/billing-entitlement.dto';
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { ILike, IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { type CreateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/create-agent.input';
import { type UpdateAgentInput } from 'src/engine/metadata-modules/ai/ai-agent/dtos/update-agent.input';
import { fromCreateAgentInputToFlatAgent } from 'src/engine/metadata-modules/ai/ai-agent/utils/from-create-agent-input-to-flat-agent.util';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import {
CommandMenuItemException,
CommandMenuItemExceptionCode,
@@ -5,7 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { isDefined } from 'twenty-shared/utils';
import { type FindOneOptions, type Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
@@ -12,7 +12,7 @@ import { Repository } from 'typeorm';
import { FileStorageExceptionCode } from 'src/engine/core-modules/file-storage/interfaces/file-storage-exception';
import { type LogicFunctionExecuteResult } from 'src/engine/core-modules/logic-function-executor/drivers/interfaces/logic-function-executor-driver.interface';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { AuditService } from 'src/engine/core-modules/audit/services/audit.service';
import { LOGIC_FUNCTION_EXECUTED_EVENT } from 'src/engine/core-modules/audit/utils/events/workspace-event/logic-function/logic-function-executed';
import { ApplicationTokenService } from 'src/engine/core-modules/auth/token/services/application-token.service';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import type { CreateLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-logic-function.input';
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { isApiKeyAuthContext } from 'src/engine/core-modules/auth/guards/is-api-key-auth-context.guard';
import { isApplicationAuthContext } from 'src/engine/core-modules/auth/guards/is-application-auth-context.guard';
import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard';
@@ -5,7 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatPageLayoutWidgetMaps } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget-maps.type';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
@@ -4,7 +4,7 @@ import { PageLayoutTabLayoutMode } from 'twenty-shared/types';
import { computeDiffBetweenObjects, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FLAT_PAGE_LAYOUT_TAB_EDITABLE_PROPERTIES } from 'src/engine/metadata-modules/flat-page-layout-tab/constants/flat-page-layout-tab-editable-properties.constant';
@@ -3,7 +3,7 @@ import { Injectable } from '@nestjs/common';
import { isNonEmptyString } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { type FlatPageLayoutTabMaps } from 'src/engine/metadata-modules/flat-page-layout-tab/types/flat-page-layout-tab-maps.type';
@@ -4,7 +4,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { PermissionFlagType } from 'twenty-shared/constants';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@@ -7,7 +7,7 @@ import { isDefined } from 'twenty-shared/utils';
import { In, Repository } from 'typeorm';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags';
import {
PermissionsException,
@@ -119,6 +119,7 @@ export class PermissionsService {
[PermissionFlagType.IMPERSONATE]: false,
[PermissionFlagType.SSO_BYPASS]: false,
[PermissionFlagType.PROFILE_INFORMATION]: false,
[PermissionFlagType.MARKETPLACE_APPS]: false,
},
objectsPermissions: {},
}) as const satisfies UserWorkspacePermissions;
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { FlatRoleTarget } from 'src/engine/metadata-modules/flat-role-target/types/flat-role-target.type';
@@ -18,7 +18,7 @@ import { PermissionFlagType } from 'twenty-shared/constants';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
@@ -6,7 +6,7 @@ import { ConfigService } from '@nestjs/config';
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum';
import { BillingService } from 'src/engine/core-modules/billing/services/billing.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -3,7 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { type Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { ViewFilterGroupEntity } from 'src/engine/metadata-modules/view-filter-group/entities/view-filter-group.entity';
import { ViewFilterGroupLogicalOperator } from 'src/engine/metadata-modules/view-filter-group/enums/view-filter-group-logical-operator';
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, type Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { fromCreateViewFilterGroupInputToFlatViewFilterGroupToCreate } from 'src/engine/metadata-modules/flat-view-filter-group/utils/from-create-view-filter-group-input-to-flat-view-filter-group-to-create.util';
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { fromCreateViewFilterInputToFlatViewFilterToCreate } from 'src/engine/metadata-modules/flat-view-filter/utils/from-create-view-filter-input-to-flat-view-filter-to-create.util';
@@ -4,7 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -3,7 +3,7 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { type Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import { ViewSortDirection } from 'src/engine/metadata-modules/view-sort/enums/view-sort-direction';
import {
@@ -5,7 +5,7 @@ import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ViewSortEntity } from 'src/engine/metadata-modules/view-sort/entities/view-sort.entity';
import {
ViewSortException,
@@ -5,7 +5,7 @@ import { APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { I18nService } from 'src/engine/core-modules/i18n/i18n.service';
import { generateMessageId } from 'src/engine/core-modules/i18n/utils/generateMessageId';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
@@ -1,11 +1,11 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { IsNull, Repository } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
import { fromCreateWebhookInputToFlatWebhookToCreate } from 'src/engine/metadata-modules/flat-webhook/utils/from-create-webhook-input-to-flat-webhook-to-create.util';
@@ -242,6 +242,7 @@ describe('WorkspaceEntityManager', () => {
IS_NAVIGATION_MENU_ITEM_ENABLED: false,
IS_FILES_FIELD_ENABLED: false,
IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED: false,
IS_MARKETPLACE_ENABLED: false,
IS_RECORD_PAGE_LAYOUT_EDITING_ENABLED: false,
},
userWorkspaceRoleMap: {},
@@ -1,6 +1,6 @@
import { type DataSource } from 'typeorm';
import { type ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { seedBillingCustomers } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-customers.util';
import { seedBillingSubscriptions } from 'src/engine/workspace-manager/dev-seeder/core/billing/utils/seed-billing-subscriptions.util';
import {
@@ -91,6 +91,11 @@ export const seedFeatureFlags = async ({
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_MARKETPLACE_ENABLED,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKey.IS_COMMAND_MENU_ITEM_ENABLED,
workspaceId: workspaceId,
@@ -4,7 +4,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { DataSource } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { MetadataFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/metadata-flat-entity.type';
@@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationService } from 'src/engine/core-modules/application/services/application.service';
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';