Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Integer Storage for ActorTypeEnum in Database #8419

Merged
merged 3 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/contracts/src/base-entity.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ export interface IBasePerTenantAndOrganizationEntityMutationInput extends Partia

// Actor type defines if it's User or system performed some action
export enum ActorTypeEnum {
System = 0, // System performed the action
User = 1 // User performed the action
System = 'System', // System performed the action
User = 'User' // User performed the action
}

export enum EntityEnum {
Expand Down
8 changes: 3 additions & 5 deletions packages/core/src/activity-log/activity-log.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ActivityLogService } from './activity-log.service';
@Permissions()
@Controller('/activity-log')
export class ActivityLogController {
constructor(readonly _activityLogService: ActivityLogService) {}
constructor(readonly _activityLogService: ActivityLogService) {}

/**
* Retrieves activity logs based on query parameters.
Expand All @@ -20,10 +20,8 @@ export class ActivityLogController {
* @returns A list of activity logs.
*/
@Get('/')
@UseValidationPipe()
async getActivityLogs(
@Query() query: GetActivityLogsDTO
): Promise<IPagination<IActivityLog>> {
@UseValidationPipe({ transform: true })
async getActivityLogs(@Query() query: GetActivityLogsDTO): Promise<IPagination<IActivityLog>> {
return await this._activityLogService.findActivityLogs(query);
}
}
9 changes: 5 additions & 4 deletions packages/core/src/activity-log/activity-log.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { isMySQL, isPostgres } from '@gauzy/config';
import { EntityEnum, ActionTypeEnum, ActorTypeEnum, IActivityLog, ID, IUser, JsonData } from '@gauzy/contracts';
import { TenantOrganizationBaseEntity, User } from '../core/entities/internal';
import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity';
import { ActorTypeTransformerPipe } from '../shared/pipes';
import { MikroOrmActivityLogRepository } from './repository/mikro-orm-activity-log.repository';

@MultiORMEntity('activity_log', { mikroOrmRepository: () => MikroOrmActivityLogRepository })
Expand All @@ -26,19 +27,19 @@ export class ActivityLog extends TenantOrganizationBaseEntity implements IActivi
@MultiORMColumn()
entityId: ID;

@ApiProperty({ type: () => String, enum: ActionTypeEnum })
@ApiProperty({ enum: ActionTypeEnum })
@IsNotEmpty()
@IsEnum(ActionTypeEnum)
@ColumnIndex()
@MultiORMColumn()
action: ActionTypeEnum;

@ApiPropertyOptional({ type: () => String, enum: ActorTypeEnum })
@ApiPropertyOptional({ enum: ActorTypeEnum })
@IsOptional()
@IsEnum(ActorTypeEnum)
@ColumnIndex()
@MultiORMColumn({ nullable: true })
actorType?: ActorTypeEnum;
@MultiORMColumn({ type: 'int', nullable: true, transformer: new ActorTypeTransformerPipe() })
actorType?: ActorTypeEnum; // Will be stored as 0 or 1 in DB

@ApiPropertyOptional({ type: () => String })
@IsOptional()
Expand Down
31 changes: 18 additions & 13 deletions packages/core/src/activity-log/activity-log.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ export class ActivityLogService extends TenantAwareCrudService<ActivityLog> {
* });
* ```
*/
public async findActivityLogs(filter: GetActivityLogsDTO): Promise<IPagination<IActivityLog>> {
public async findActivityLogs(filters: GetActivityLogsDTO): Promise<IPagination<IActivityLog>> {
const {
organizationId,
entity,
entityId,
action,
Expand All @@ -42,17 +43,16 @@ export class ActivityLogService extends TenantAwareCrudService<ActivityLog> {
isArchived = false,
orderBy = 'createdAt',
order = 'DESC',
relations = [],
skip,
take
} = filter;
relations = []
} = filters;

// Build the 'where' condition using concise syntax
const where: FindOptionsWhere<ActivityLog> = {
...(entity && { entity }),
...(entityId && { entityId }),
...(action && { action }),
...(actorType && { actorType }),
...(actorType !== undefined && actorType !== null && { actorType }), // Ensure 0 is not ignored
organizationId,
isActive,
isArchived
};
Expand All @@ -64,17 +64,22 @@ export class ActivityLogService extends TenantAwareCrudService<ActivityLog> {
// Define order option
const orderOption: FindOptionsOrder<ActivityLog> = { [orderField]: orderDirection };

// Define find options
const findOptions: FindManyOptions<ActivityLog> = {
// Ensure that filters are properly defined
const queryOptions: FindManyOptions<ActivityLog> = {
where,
order: orderOption,
...(skip && { skip }),
...(take && { take }),
...(relations && { relations })
...(relations && { relations }),
take: filters.take ?? 100, // Default to 100 if not provided
skip: filters.skip ? filters.take * (filters.skip - 1) : 0 // Calculate offset
};

// Apply sorting options (if provided)
if (filters.order) {
queryOptions.order = orderOption; // Order, in which entities should be ordered. Default to ASC if no order is provided.
}

console.log('queryOptions', queryOptions);
// Retrieve activity logs using the base class method
return await super.findAll(findOptions);
return await super.findAll(queryOptions);
}

/**
Expand Down
10 changes: 2 additions & 8 deletions packages/core/src/activity-log/dto/get-activity-logs.dto.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ApiPropertyOptional, IntersectionType, PickType } from '@nestjs/swagger';
import { IsEnum, IsIn, IsOptional, IsString, IsUUID } from 'class-validator';
import { ActionTypeEnum, EntityEnum, ActorTypeEnum, ID } from '@gauzy/contracts';
import { ActionTypeEnum, EntityEnum, ID } from '@gauzy/contracts';
import { PaginationParams } from '../../core/crud';
import { TenantOrganizationBaseDTO } from '../../core/dto';
import { ActivityLog } from '../activity-log.entity';
Expand All @@ -15,7 +15,7 @@ export const allowedOrderDirections = ['ASC', 'DESC', 'asc', 'desc'];
export class GetActivityLogsDTO extends IntersectionType(
TenantOrganizationBaseDTO,
PickType(PaginationParams<ActivityLog>, ['skip', 'take', 'relations']),
PickType(ActivityLog, ['isActive', 'isArchived'])
PickType(ActivityLog, ['isActive', 'isArchived', 'actorType'])
) {
// Filter by entity (example: Organization, Task, OrganizationContact)
@ApiPropertyOptional({ enum: EntityEnum })
Expand All @@ -35,12 +35,6 @@ export class GetActivityLogsDTO extends IntersectionType(
@IsEnum(ActionTypeEnum)
action: ActionTypeEnum;

// Filter by actorType (example: SYSTEM, USER)
@ApiPropertyOptional({ type: () => String, enum: ActorTypeEnum })
@IsOptional()
@IsEnum(ActorTypeEnum)
actorType?: ActorTypeEnum;

// Filter by orderBy (example: createdAt, updatedAt, entity, action)
@ApiPropertyOptional({ type: () => String, enum: allowedOrderFields })
@IsOptional()
Expand Down
9 changes: 5 additions & 4 deletions packages/core/src/comment/comment.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
MultiORMManyToOne,
MultiORMOneToMany
} from '../core/decorators/entity';
import { ActorTypeTransformerPipe } from '../shared/pipes';
import { MikroOrmCommentRepository } from './repository/mikro-orm-comment.repository';

@MultiORMEntity('comment', { mikroOrmRepository: () => MikroOrmCommentRepository })
Expand Down Expand Up @@ -40,12 +41,12 @@ export class Comment extends TenantOrganizationBaseEntity implements IComment {
@MultiORMColumn({ type: 'text' })
comment: string;

@ApiPropertyOptional({ type: () => String, enum: ActorTypeEnum })
@IsNotEmpty()
@ApiPropertyOptional({ enum: ActorTypeEnum })
@IsOptional()
@IsEnum(ActorTypeEnum)
@ColumnIndex()
@MultiORMColumn({ nullable: true })
actorType?: ActorTypeEnum;
@MultiORMColumn({ type: 'int', nullable: true, transformer: new ActorTypeTransformerPipe() })
actorType?: ActorTypeEnum; // Will be stored as 0 or 1 in DB

@ApiPropertyOptional({ type: Boolean })
@IsOptional()
Expand Down
33 changes: 33 additions & 0 deletions packages/core/src/shared/pipes/actor-type-transform.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Logger } from '@nestjs/common';
import { ValueTransformer } from 'typeorm';
import { ActorTypeEnum } from '@gauzy/contracts';

/**
* ActorTypeTransformerPipe handles the conversion between the enum string values
* (used in the application) and the integer values (stored in the database).
*/
export class ActorTypeTransformerPipe implements ValueTransformer {
private readonly logger = new Logger(ActorTypeTransformerPipe.name);

/**
* Converts the enum string value to its integer representation when writing to the database.
*
* @param value - The `ActorTypeEnum` value ('System' or 'User').
* @returns The corresponding integer value to be stored in the database (0 for System, 1 for User).
*/
to(value: ActorTypeEnum): number {
this.logger.debug(`ActorTypeTransformerPipe: converting ${value} to integer`);
return value === ActorTypeEnum.User ? 1 : 0; // 1 for 'User', 0 for 'System' (default)
}

/**
* Converts the integer value to its corresponding `ActorTypeEnum` string when reading from the database.
*
* @param value - The integer value (0 or 1) from the database.
* @returns The corresponding `ActorTypeEnum` ('System' for 0, 'User' for 1).
*/
from(value: number): ActorTypeEnum {
this.logger.debug(`ActorTypeTransformerPipe: converting ${value} to enum`);
return value === 1 ? ActorTypeEnum.User : ActorTypeEnum.System;
}
}
9 changes: 5 additions & 4 deletions packages/core/src/shared/pipes/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
export * from './uuid-validation.pipe';
export * from './parse-json.pipe';
export * from './abstract-validation.pipe';
export * from './actor-type-transform.pipe';
export * from './bulk-body-load-transform.pipe';
export * from './column-numeric-transformer.pipe';
export * from './abstract-validation.pipe';
export * from './use-validation-pipe.pipe';
export * from './http-method-transformer.pipe';
export * from './parse-json.pipe';
export * from './use-validation.pipe';
export * from './uuid-validation.pipe';
5 changes: 0 additions & 5 deletions packages/core/src/shared/pipes/use-validation-pipe.pipe.ts

This file was deleted.

15 changes: 15 additions & 0 deletions packages/core/src/shared/pipes/use-validation.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UsePipes, ValidationPipe, ValidationPipeOptions } from '@nestjs/common';

/**
* Creates and applies a custom validation pipe with optional configuration.
*
* This function is a helper for applying NestJS's `ValidationPipe` with custom options
* to a route or controller. It wraps the `UsePipes` decorator and makes it easier to
* customize validation behavior.
*
* @param options - Optional `ValidationPipeOptions` to customize the validation behavior.
* @returns A decorator that applies the `ValidationPipe` with the given options.
*/
export function UseValidationPipe(options?: Partial<ValidationPipeOptions>) {
return UsePipes(new ValidationPipe(options ?? {}));
}
Loading