Refactor application module architecture for clarity and explicitness (#18432)
## Summary - **Module reorganization**: Moved `ApplicationUpgradeService` and cron jobs to `application-upgrade/`, `ApplicationSyncService` to `application-manifest/`, and `runWorkspaceMigration`/`uninstallApplication` mutations to the manifest resolver — each module now has a single clear responsibility. - **Explicit install flow**: Removed implicit `ApplicationEntity` creation from `ApplicationSyncService`. The install service and dev resolver now explicitly create the `ApplicationEntity` before syncing. npm packages are resolved at registration time to extract manifest metadata (universalIdentifier, name, description, etc.), eliminating the `reconcileUniversalIdentifier` hack. - **Better error handling**: Frontend hooks now surface actual server error messages in snackbars instead of swallowing them. Replaced the ugly `ConfirmationModal` for transfer ownership with a proper form modal. Fixed `SettingsAdminTableCard` row height overflow and corrected the `yarn-engine` asset path. ## Test plan - [ ] Register an npm package — verify manifest metadata (name, description, universalIdentifier) is extracted correctly - [ ] Install a registered npm app on a workspace — verify ApplicationEntity is created and sync succeeds - [ ] Test `app:dev` CLI flow — verify local app registration and sync work - [ ] Upload a tarball — verify registration and install flow - [ ] Transfer ownership — verify the new modal UX works - [ ] Verify error messages appear correctly in snackbars when operations fail Made with [Cursor](https://cursor.com)
This commit is contained in:
+3
-5
@@ -42,14 +42,12 @@ export class AgentTurnResolver {
|
||||
@Query(() => [AgentTurnDTO])
|
||||
async agentTurns(
|
||||
@Args('agentId', { type: () => UUIDScalarType }) agentId: string,
|
||||
): Promise<AgentTurnDTO[]> {
|
||||
const turns = await this.turnRepository.find({
|
||||
): Promise<AgentTurnEntity[]> {
|
||||
return this.turnRepository.find({
|
||||
where: { agentId },
|
||||
relations: ['evaluations', 'messages', 'messages.parts'],
|
||||
order: { createdAt: 'DESC' },
|
||||
});
|
||||
|
||||
return turns;
|
||||
}
|
||||
|
||||
@Mutation(() => AgentTurnEvaluationDTO)
|
||||
@@ -67,7 +65,7 @@ export class AgentTurnResolver {
|
||||
@Args('input') input: string,
|
||||
@AuthWorkspace() workspace: WorkspaceEntity,
|
||||
@AuthUserWorkspaceId() userWorkspaceId: string,
|
||||
): Promise<AgentTurnDTO> {
|
||||
): Promise<AgentTurnEntity> {
|
||||
const thread = this.threadRepository.create({
|
||||
userWorkspaceId,
|
||||
title: `Eval: ${input.substring(0, 50)}...`,
|
||||
|
||||
+2
-2
@@ -5,7 +5,7 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
@@ -27,7 +27,7 @@ const MOCK_FLAT_APPLICATION: FlatApplication = {
|
||||
description: null,
|
||||
workspaceId: 'workspace-id',
|
||||
version: null,
|
||||
sourceType: AppRegistrationSourceType.LOCAL,
|
||||
sourceType: ApplicationRegistrationSourceType.LOCAL,
|
||||
sourcePath: '',
|
||||
packageJsonChecksum: null,
|
||||
packageJsonFileId: null,
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ import {
|
||||
RelationType,
|
||||
} from 'twenty-shared/types';
|
||||
|
||||
import { AppRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/app-registration-source-type.enum';
|
||||
import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/application/application-registration/enums/application-registration-source-type.enum';
|
||||
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
|
||||
import {
|
||||
generateMorphOrRelationFlatFieldMetadataPair,
|
||||
@@ -22,7 +22,7 @@ const MOCK_FLAT_APPLICATION: FlatApplication = {
|
||||
description: null,
|
||||
version: null,
|
||||
workspaceId: 'workspace-id',
|
||||
sourceType: AppRegistrationSourceType.LOCAL,
|
||||
sourceType: ApplicationRegistrationSourceType.LOCAL,
|
||||
sourcePath: '',
|
||||
packageJsonChecksum: null,
|
||||
packageJsonFileId: null,
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ import {
|
||||
} from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/dtos/application-token-pair.dto';
|
||||
import { ApplicationTokenPairDTO } from 'src/engine/core-modules/application/application-oauth/dtos/application-token-pair.dto';
|
||||
|
||||
@ObjectType('FrontComponent')
|
||||
export class FrontComponentDTO {
|
||||
|
||||
+2
-2
@@ -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 { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
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';
|
||||
@@ -34,7 +34,7 @@ describe('PermissionsService', () => {
|
||||
useValue: {},
|
||||
},
|
||||
{
|
||||
provide: ApplicationService,
|
||||
provide: getRepositoryToken(ApplicationEntity),
|
||||
useValue: {},
|
||||
},
|
||||
],
|
||||
|
||||
@@ -4,7 +4,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ApiKeyEntity } from 'src/engine/core-modules/api-key/api-key.entity';
|
||||
import { ApiKeyRoleService } from 'src/engine/core-modules/api-key/services/api-key-role.service';
|
||||
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
|
||||
import { PermissionsService } from 'src/engine/metadata-modules/permissions/permissions.service';
|
||||
@@ -27,7 +26,6 @@ import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache
|
||||
UserRoleModule,
|
||||
WorkspaceCacheModule,
|
||||
RoleTargetModule,
|
||||
ApplicationModule,
|
||||
],
|
||||
providers: [ApiKeyRoleService, PermissionsService],
|
||||
exports: [PermissionsService, ApiKeyRoleService],
|
||||
|
||||
+18
-6
@@ -7,7 +7,11 @@ 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 { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import {
|
||||
ApplicationException,
|
||||
ApplicationExceptionCode,
|
||||
} from 'src/engine/core-modules/application/application.exception';
|
||||
import { TOOL_PERMISSION_FLAGS } from 'src/engine/metadata-modules/permissions/constants/tool-permission-flags';
|
||||
import {
|
||||
PermissionsException,
|
||||
@@ -28,7 +32,8 @@ export class PermissionsService {
|
||||
private readonly apiKeyRoleService: ApiKeyRoleService,
|
||||
@InjectRepository(RoleEntity)
|
||||
private readonly roleRepository: Repository<RoleEntity>,
|
||||
private readonly applicationService: ApplicationService,
|
||||
@InjectRepository(ApplicationEntity)
|
||||
private readonly applicationRepository: Repository<ApplicationEntity>,
|
||||
) {}
|
||||
|
||||
private isToolPermission(feature: string) {
|
||||
@@ -183,11 +188,18 @@ export class PermissionsService {
|
||||
}
|
||||
|
||||
if (applicationId) {
|
||||
const applicationRoleId =
|
||||
await this.applicationService.findApplicationRoleId(
|
||||
applicationId,
|
||||
workspaceId,
|
||||
const application = await this.applicationRepository.findOne({
|
||||
where: { id: applicationId, workspaceId },
|
||||
});
|
||||
|
||||
if (!isDefined(application) || !isDefined(application.defaultRoleId)) {
|
||||
throw new ApplicationException(
|
||||
`Could not find application ${applicationId}`,
|
||||
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const applicationRoleId = application.defaultRoleId;
|
||||
|
||||
const role = await this.roleRepository.findOne({
|
||||
where: { id: applicationRoleId, workspaceId },
|
||||
|
||||
Reference in New Issue
Block a user