mirror of
https://github.com/ever-co/ever-gauzy.git
synced 2026-10-02 01:54:50 +08:00
[Fix] AI suggestion stage (#8996)
* chore(cspell): remove typo from cspell dictionary * chore(camshot): update delete command type * refactor(camshot): include id in camshot not found error message * docs(config): add jsdoc to activepieces property * refactor(camshot): standardize find options type for findById Replace TypeORM's FindOneOptions with the application's standardized FindOptionsQueryDTO for consistency in query parameter handling. * feat(integration-activepieces): use dto for activepieces query params Create ActivepiecesQueryDto to define the structure, typing, and validation for query parameters handled by the ActivepiecesAuthorizationController. Replace the 'any' type annotation with ActivepiecesQueryDto in the authorize and callback methods. This improves type safety and enables automatic validation and Swagger documentation generation for the API endpoints. * chore(integration-activepieces): remove empty entities array from plugin config The `entities` array in the Activepieces VendurePlugin configuration was declared but empty and served no purpose. Remove the array and the associated comment to clean up the configuration. * fix(activepieces): improve error handling for connection Update error handling in `createActivePiecesConnection` to provide more specific exception types. Catch and re-throw `UnauthorizedException` explicitly, and wrap other errors in `InternalServerErrorException` for clearer reporting downstream. * docs(common): fix typo in callback url documentation * refactor(activepieces): remove unused ID import * style(camshot): improve not found error message Add 'id' before the variable for clarity. Adjust formatting in the constructor. * docs(camshot): add api docs for get camshot by id add @ApiResponse decorators for status codes 200, 404, and 500 add @ApiQuery decorator for the optional options parameter * refactor(activepieces): rename query dto and add validation Rename ActivepiecesQueryDto to ActivepiecesQueryDTO for naming consistency. Add ValidationPipe to the authorize endpoint to ensure incoming query parameters are validated against the DTO schema. This improves input handling robustness. * fix(activepieces): correctly handle boolean setting values The previous implementation of `isIntegrationEnabled` only handled boolean values stored as JSON strings. This update adds support for boolean values stored as native booleans or other types, ensuring the check works correctly regardless of how the setting value is stored. * fix(activepieces): fix error handling and reporting Extract detailed error messages from the ActivePieces API response body when handling errors during connection creation. This provides users with more specific information about why a connection failed. Also, ensure that specific HTTP exceptions caught within the HTTP request observable's catchError are not subsequently wrapped in a BadRequestException by the outer try-catch block. This preserves the original HTTP status code and error type.
This commit is contained in:
@@ -3,7 +3,6 @@
|
||||
"language": "en",
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
||||
"words": [
|
||||
"accesibility",
|
||||
"activitywatch",
|
||||
"adminer",
|
||||
"activepieces",
|
||||
|
||||
@@ -19,7 +19,7 @@ export interface IActivepiecesConfig {
|
||||
|
||||
/**
|
||||
* The redirect URI registered with Activepieces for OAuth callback.
|
||||
* This URL receives he authorization code during the OAuth.
|
||||
* This URL receives the authorization code during the OAuth.
|
||||
*/
|
||||
readonly callbackUrl: string;
|
||||
|
||||
|
||||
@@ -124,8 +124,8 @@ export interface IEnvironment {
|
||||
hubstaff?: IHubstaffConfig;
|
||||
zapier?: IZapierConfig;
|
||||
makeCom?: IMakeComConfig;
|
||||
activepieces?: IActivepiecesConfig; // ActivePieces Configuration
|
||||
|
||||
/** ActivePieces Configuration */
|
||||
activepieces?: IActivepiecesConfig;
|
||||
isElectron?: boolean;
|
||||
gauzyUserPath?: string;
|
||||
allowSuperAdminRole?: boolean;
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
BaseQueryDTO,
|
||||
FileStorage,
|
||||
FileStorageFactory,
|
||||
FindOptionsQueryDTO,
|
||||
LazyFileInterceptor,
|
||||
PermissionGuard,
|
||||
Permissions,
|
||||
@@ -26,7 +27,6 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { CommandBus, QueryBus } from '@nestjs/cqrs';
|
||||
import { ApiConsumes, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { FindOneOptions } from 'typeorm';
|
||||
import { CreateCamshotCommand } from './commands/create-camshot.command';
|
||||
import { DeleteCamshotCommand } from './commands/delete-camshot.command';
|
||||
import { CountCamshotDTO } from './dtos/count-camshot.dto';
|
||||
@@ -43,7 +43,7 @@ import { GetCamshotQuery } from './queries/get-camshot.query';
|
||||
@Permissions(PermissionsEnum.TIME_TRACKER)
|
||||
@Controller('/plugins/camshots')
|
||||
export class CamshotController {
|
||||
constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) {}
|
||||
constructor(private readonly commandBus: CommandBus, private readonly queryBus: QueryBus) { }
|
||||
|
||||
/**
|
||||
* Get a paginated list of camshots.
|
||||
@@ -184,6 +184,19 @@ export class CamshotController {
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: 'Invalid input, The response body may contain clues as to what went wrong'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
description: 'Camshot successfully fetched by ID.'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.NOT_FOUND,
|
||||
description: 'Camshot with the given ID not found.'
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: 'An error occurred while retrieving the camshot.'
|
||||
})
|
||||
@ApiQuery({ name: 'options', type: FindOptionsQueryDTO, required: false })
|
||||
@UseValidationPipe({
|
||||
whitelist: true,
|
||||
transform: true,
|
||||
@@ -192,7 +205,7 @@ export class CamshotController {
|
||||
@Get(':id')
|
||||
public async findById(
|
||||
@Param('id', UUIDValidationPipe) id: ID,
|
||||
@Query() options: FindOneOptions<ICamshot>
|
||||
@Query() options: FindOptionsQueryDTO<ICamshot>
|
||||
): Promise<ICamshot> {
|
||||
return this.queryBus.execute(new GetCamshotQuery(id, options));
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ICommand } from "@nestjs/cqrs";
|
||||
import { DeleteCamshotDTO } from "../dtos/delete-camshot.dto";
|
||||
import { ID } from "@gauzy/contracts";
|
||||
import { ICommand } from '@nestjs/cqrs';
|
||||
import { DeleteCamshotDTO } from '../dtos/delete-camshot.dto';
|
||||
import { ID } from '@gauzy/contracts';
|
||||
|
||||
export class DeleteCamshotCommand implements ICommand {
|
||||
public static readonly type = '[Delete] Camshot';
|
||||
constructor(public readonly id: ID, public readonly input: DeleteCamshotDTO) { }
|
||||
public static readonly type = '[Camshot] Delete';
|
||||
constructor(public readonly id: ID, public readonly input: DeleteCamshotDTO) {}
|
||||
}
|
||||
|
||||
+6
-6
@@ -1,8 +1,8 @@
|
||||
import { ICommandHandler, CommandHandler } from "@nestjs/cqrs";
|
||||
import { CamshotService } from "../../services/camshot.service";
|
||||
import { DeleteCamshotCommand } from "../delete-camshot.command";
|
||||
import { NotFoundException } from "@nestjs/common";
|
||||
import { RequestContext } from "@gauzy/core";
|
||||
import { ICommandHandler, CommandHandler } from '@nestjs/cqrs';
|
||||
import { CamshotService } from '../../services/camshot.service';
|
||||
import { DeleteCamshotCommand } from '../delete-camshot.command';
|
||||
import { NotFoundException } from '@nestjs/common';
|
||||
import { RequestContext } from '@gauzy/core';
|
||||
|
||||
@CommandHandler(DeleteCamshotCommand)
|
||||
export class DeleteCamshotCommandHandler implements ICommandHandler<DeleteCamshotCommand> {
|
||||
@@ -24,7 +24,7 @@ export class DeleteCamshotCommandHandler implements ICommandHandler<DeleteCamsho
|
||||
const camshot = await this.camshotService.findOneByWhereOptions({ id, organizationId, tenantId });
|
||||
|
||||
if (!camshot) {
|
||||
throw new NotFoundException('Camshot not found');
|
||||
throw new NotFoundException(`Camshot with id ${id} not found`);
|
||||
}
|
||||
|
||||
if (forceDelete) {
|
||||
|
||||
+5
-3
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, HttpException, HttpStatus, Query, Res } from '@nestjs/common';
|
||||
import { Controller, Get, HttpException, HttpStatus, Query, Res, UsePipes, ValidationPipe } from '@nestjs/common';
|
||||
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
@@ -6,6 +6,7 @@ import { Public, IActivepiecesConfig } from '@gauzy/common';
|
||||
import { IntegrationEnum } from '@gauzy/contracts';
|
||||
import { buildQueryString } from '@gauzy/utils';
|
||||
import { ACTIVEPIECES_OAUTH_AUTHORIZE_URL, ACTIVEPIECES_SCOPES, OAUTH_RESPONSE_TYPE } from './activepieces.config';
|
||||
import { ActivepiecesQueryDTO } from './dto/activepieces-query.dto';
|
||||
|
||||
@ApiTags('ActivePieces Integration')
|
||||
@Public()
|
||||
@@ -46,7 +47,8 @@ export class ActivepiecesAuthorizationController {
|
||||
}
|
||||
})
|
||||
@Get('/authorize')
|
||||
async authorize(@Query() query: any, @Res() response: Response) {
|
||||
@UsePipes(new ValidationPipe({ whitelist: true, transform: true }))
|
||||
async authorize(@Query() query: ActivepiecesQueryDTO, @Res() response: Response) {
|
||||
try {
|
||||
// Get ActivePieces configuration
|
||||
const activepiecesConfig = this.configService.get('activepieces') as IActivepiecesConfig;
|
||||
@@ -95,7 +97,7 @@ export class ActivepiecesAuthorizationController {
|
||||
description: 'Redirects to the application with authorization code'
|
||||
})
|
||||
@Get('/callback')
|
||||
async callback(@Query() query: any, @Res() response: Response) {
|
||||
async callback(@Query() query: ActivepiecesQueryDTO, @Res() response: Response) {
|
||||
try {
|
||||
// Validate the input data
|
||||
if (!query || !query.code || !query.state) {
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Injectable, BadRequestException, Logger, HttpException, HttpStatus, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
Injectable,
|
||||
BadRequestException,
|
||||
Logger,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
UnauthorizedException,
|
||||
InternalServerErrorException
|
||||
} from '@nestjs/common';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { ConfigService } from '@gauzy/config';
|
||||
import { firstValueFrom, catchError, throwError } from 'rxjs';
|
||||
@@ -10,7 +18,8 @@ import {
|
||||
IActivepiecesConnectionRequest,
|
||||
ActivepiecesSettingName,
|
||||
ActivepiecesConnectionType,
|
||||
ICreateActivepiecesIntegrationInput
|
||||
ICreateActivepiecesIntegrationInput,
|
||||
IActivepiecesErrorResponse
|
||||
} from './activepieces.type';
|
||||
import { ACTIVEPIECES_CONNECTIONS_URL, ACTIVEPIECES_PIECE_NAME } from './activepieces.config';
|
||||
|
||||
@@ -24,7 +33,7 @@ export class ActivepiecesService {
|
||||
private readonly integrationSettingService: IntegrationSettingService,
|
||||
private readonly integrationService: IntegrationService,
|
||||
private readonly integrationTenantService: IntegrationTenantService
|
||||
) {}
|
||||
) { }
|
||||
|
||||
/**
|
||||
* Create a new ActivePieces connection for the tenant
|
||||
@@ -85,15 +94,29 @@ export class ActivepiecesService {
|
||||
}
|
||||
})
|
||||
.pipe(
|
||||
catchError((error: AxiosError) => {
|
||||
this.logger.error('Error creating ActivePieces connection:', error.response?.data);
|
||||
const status = error.response?.status;
|
||||
catchError((error) => {
|
||||
const status = error?.response?.status;
|
||||
const data = error?.response?.data as IActivepiecesErrorResponse;
|
||||
const errorMessage = data?.error?.message || error.message;
|
||||
|
||||
this.logger.error('Error creating ActivePieces connection:', data);
|
||||
|
||||
if (status === HttpStatus.UNAUTHORIZED) {
|
||||
throw new UnauthorizedException(
|
||||
`Unauthorized to create ActivePieces connection: ${error.message}`
|
||||
)
|
||||
return throwError(
|
||||
() =>
|
||||
new UnauthorizedException(
|
||||
data?.error?.message ?? `Unauthorized to create ActivePieces connection: ${error.message}`
|
||||
)
|
||||
);
|
||||
}
|
||||
return throwError(() => error);
|
||||
|
||||
// Optionally wrap and throw a more descriptive internal server error
|
||||
return throwError(
|
||||
() =>
|
||||
new InternalServerErrorException(
|
||||
`Failed to create ActivePieces connection: ${errorMessage}`
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
@@ -104,6 +127,9 @@ export class ActivepiecesService {
|
||||
this.logger.log(`Successfully created ActivePieces connection: ${response.data.id}`);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
if (error instanceof HttpException) {
|
||||
throw error;
|
||||
}
|
||||
this.logger.error('Failed to create ActivePieces connection:', error);
|
||||
throw new BadRequestException(`Failed to create connection: ${error.message}`);
|
||||
}
|
||||
@@ -249,7 +275,12 @@ export class ActivepiecesService {
|
||||
}
|
||||
});
|
||||
|
||||
return enabledSetting?.settingsValue ? JSON.parse(enabledSetting.settingsValue) === true: false;
|
||||
if (typeof enabledSetting?.settingsValue === 'boolean') {
|
||||
return enabledSetting.settingsValue;
|
||||
}
|
||||
return !!(typeof enabledSetting?.settingsValue === 'string'
|
||||
? JSON.parse(enabledSetting.settingsValue)
|
||||
: enabledSetting?.settingsValue);
|
||||
} catch (error) {
|
||||
this.logger.error('Error checking if integration is enabled:', error);
|
||||
return false;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { IBasePerTenantAndOrganizationEntityModel, ID } from '@gauzy/contracts';
|
||||
import { IBasePerTenantAndOrganizationEntityModel } from '@gauzy/contracts';
|
||||
|
||||
/**
|
||||
* ActivePieces OAuth token response interface
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
|
||||
export class ActivepiecesQueryDTO {
|
||||
@ApiProperty({
|
||||
description: 'The state parameter for CSRF protection',
|
||||
required: false,
|
||||
type: String
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
readonly state?: string;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'The authorization code received from ActivePieces',
|
||||
required: false,
|
||||
type: String
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
readonly code?: string;
|
||||
}
|
||||
@@ -7,11 +7,6 @@ import { ActivepiecesModule } from './activepieces.module';
|
||||
* An array of modules that will be imported and registered with the plugin.
|
||||
*/
|
||||
imports: [ActivepiecesModule],
|
||||
/**
|
||||
* Entity needed for ActivePieces integration that extends the existing
|
||||
* IntegrationSetting entity to store connection data
|
||||
*/
|
||||
entities: [],
|
||||
/**
|
||||
* A callback that receives the main plugin configuration object and allows
|
||||
* custom modifications before returning the final configuration.
|
||||
|
||||
Reference in New Issue
Block a user