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 all commits
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
49 changes: 28 additions & 21 deletions packages/core/src/activity-log/activity-log.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { FindManyOptions, FindOptionsOrder, FindOptionsWhere } from 'typeorm';
import { IActivityLog, IActivityLogInput, IPagination } from '@gauzy/contracts';
import { isNotNullOrUndefined } from '@gauzy/common';
import { TenantAwareCrudService } from './../core/crud';
import { RequestContext } from '../core/context';
import { GetActivityLogsDTO, allowedOrderDirections, allowedOrderFields } from './dto/get-activity-logs.dto';
Expand All @@ -17,9 +18,9 @@ export class ActivityLogService extends TenantAwareCrudService<ActivityLog> {
}

/**
* Finds and retrieves activity logs based on the given filter criteria.
* Finds and retrieves activity logs based on the given filters criteria.
*
* @param {GetActivityLogsDTO} filter - Filter criteria to find activity logs, including entity, entityId, action, actorType, isActive, isArchived, orderBy, and order.
* @param {GetActivityLogsDTO} filters - Filter criteria to find activity logs, including entity, entityId, action, actorType, isActive, isArchived, orderBy, and order.
* @returns {Promise<IPagination<IActivityLog>>} - A promise that resolves to a paginated list of activity logs.
*
* Example usage:
Expand All @@ -32,8 +33,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,39 +44,44 @@ export class ActivityLogService extends TenantAwareCrudService<ActivityLog> {
isArchived = false,
orderBy = 'createdAt',
order = 'DESC',
relations = [],
skip,
take
} = filter;
relations = []
} = filters;

// Fallback to default if invalid orderBy/order values are provided
const orderField = allowedOrderFields.includes(orderBy) ? orderBy : 'createdAt';
const orderDirection = allowedOrderDirections.includes(order.toUpperCase()) ? order.toUpperCase() : 'DESC';

// Define order option
const orderOption: FindOptionsOrder<ActivityLog> = { [orderField]: orderDirection };

// Build the 'where' condition using concise syntax
const where: FindOptionsWhere<ActivityLog> = {
...(organizationId && { organizationId }),
...(entity && { entity }),
...(entityId && { entityId }),
...(action && { action }),
...(actorType && { actorType }),
...(isNotNullOrUndefined(actorType) && { actorType }),
isActive,
isArchived
};

// Fallback to default if invalid orderBy/order values are provided
const orderField = allowedOrderFields.includes(orderBy) ? orderBy : 'createdAt';
const orderDirection = allowedOrderDirections.includes(order.toUpperCase()) ? order.toUpperCase() : 'DESC';
const take = filters.take ? filters.take : 100; // Default take value if not provided
// Pagination: ensure `filters.skip` is a positive integer starting from 1
const skip = (filters.skip && Number.isInteger(filters.skip) && filters.skip > 0) ? filters.skip : 1;

// 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: take,
skip: take * (skip - 1) // Calculate offset (skip) based on validated skip value
};

// Apply sorting options (if provided)
queryOptions.order = orderOption;

// 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
28 changes: 28 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,28 @@
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 {
/**
* 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 {
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 {
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