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] Alter Resource Link Entity Based On Employee #8772

Merged
merged 13 commits into from
Feb 17, 2025
Merged
Show file tree
Hide file tree
Changes from 6 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
15 changes: 6 additions & 9 deletions packages/contracts/src/lib/resource-link.model.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { IURLMetaData } from './timesheet.model';
import { IBasePerEntityType, ID } from './base-entity.model';
import { IUser } from './user.model';
import { IBasePerEntityType, OmitFields } from './base-entity.model';
import { IEmployeeEntityInput } from './employee.model';

export interface IResourceLink extends IBasePerEntityType {
export interface IResourceLink extends IBasePerEntityType, IEmployeeEntityInput {
title: string;
url: string;
creator?: IUser;
creatorId?: ID;
metaData?: string | IURLMetaData;
}

export interface IResourceLinkCreateInput extends Omit<IResourceLink, 'creator' | 'creatorId'> {}
export interface IResourceLinkCreateInput extends OmitFields<IResourceLink> {}

export interface IResourceLinkUpdateInput extends Partial<Omit<IResourceLinkCreateInput, 'entity' | 'entityId'>> {}

export interface IResourceLinkFindInput extends Pick<IResourceLink, 'entity' | 'entityId'> {}
export interface IResourceLinkUpdateInput
extends Partial<OmitFields<IResourceLinkCreateInput, 'employee' | 'employeeId' | 'entity' | 'entityId'>> {}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IntersectionType, OmitType } from '@nestjs/swagger';
import { IntersectionType } from '@nestjs/swagger';
import { IResourceLinkCreateInput } from '@gauzy/contracts';
import { TenantOrganizationBaseDTO } from '../../core/dto';
import { ResourceLink } from '../resource-link.entity';
Expand All @@ -7,5 +7,5 @@ import { ResourceLink } from '../resource-link.entity';
* Create ResourceLink data validation request DTO
*/
export class CreateResourceLinkDTO
extends IntersectionType(TenantOrganizationBaseDTO, OmitType(ResourceLink, ['creatorId', 'creator']))
extends IntersectionType(TenantOrganizationBaseDTO, ResourceLink)
implements IResourceLinkCreateInput {}
75 changes: 61 additions & 14 deletions packages/core/src/lib/resource-link/resource-link.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ export class ResourceLinkController extends CrudController<ResourceLink> {
super(resourceLinkService);
}

/**
* @description Retrieves all resource links, optionally filtered by type.
* This endpoint supports pagination and returns a list of resource links.
*
* @param {PaginationParams<ResourceLink>} params - The pagination and filter parameters.
* @returns {Promise<IPagination<IResourceLink>>} - A promise that resolves to a paginated list of resource links.
* @memberof ResourceLinkController
*/
@ApiOperation({
summary: 'Find all resource links filtered by type.'
})
Expand All @@ -46,54 +54,84 @@ export class ResourceLinkController extends CrudController<ResourceLink> {
@Get()
@UseValidationPipe()
async findAll(@Query() params: PaginationParams<ResourceLink>): Promise<IPagination<IResourceLink>> {
// Call the service to retrieve the paginated list of resource links
return await this.resourceLinkService.findAll(params);
}

@ApiOperation({ summary: 'Find by id' })
/**
* @description Retrieves a single resource link by its ID.
* This endpoint returns a resource link by its unique identifier, optionally filtered by query parameters.
*
* @param {ID} id - The unique identifier of the resource link to retrieve.
* @param {OptionParams<ResourceLink>} params - The optional query parameters for filtering or additional options.
* @returns {Promise<ResourceLink>} - A promise that resolves to the found resource link.
* @memberof ResourceLinkController
*/
@ApiOperation({ summary: 'Find resource link by ID' })
@ApiResponse({
status: HttpStatus.OK,
description: 'Found one record' /*, type: T*/
description: 'Found one resource link'
// type: ResourceLink // Uncomment and specify the type if needed for API documentation
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Record not found'
description: 'Resource link not found'
})
@Get(':id')
async findById(
@Param('id', UUIDValidationPipe) id: ID,
@Query() params: OptionParams<ResourceLink>
@Param('id', UUIDValidationPipe) id: ID, // Validate and retrieve the ID parameter
@Query() params: OptionParams<ResourceLink> // Retrieve optional query parameters
): Promise<ResourceLink> {
// Call the service to find the resource link by ID, applying optional filters if present
return this.resourceLinkService.findOneByIdString(id, params);
}

@ApiOperation({ summary: 'Create a resource link' })
/**
* @description Creates a new resource link.
* This endpoint receives the data for a resource link, validates it, and creates a new record.
*
* @param {CreateResourceLinkDTO} entity - The data to create a new resource link.
* @returns {Promise<IResourceLink>} - A promise that resolves to the created resource link.
* @memberof ResourceLinkController
*/
@ApiOperation({ summary: 'Create a new resource link' })
@ApiResponse({
status: HttpStatus.CREATED,
description: 'The record has been successfully created.'
description: 'The resource link has been successfully created.'
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid input, The response body may contain clues as to what went wrong'
description: 'Invalid input. The response body may contain details on what went wrong.'
})
@HttpCode(HttpStatus.ACCEPTED)
@Post()
@UseValidationPipe({ whitelist: true })
async create(@Body() entity: CreateResourceLinkDTO): Promise<IResourceLink> {
// Execute the command to create the resource link
return await this.commandBus.execute(new ResourceLinkCreateCommand(entity));
}

/**
* @description Updates an existing resource link by its ID.
* This endpoint receives the updated data for a resource link and updates the record in the database.
*
* @param {ID} id - The unique identifier of the resource link to update.
* @param {UpdateResourceLinkDTO} entity - The data to update the resource link.
* @returns {Promise<IResourceLinkUpdateInput>} - A promise that resolves to the updated resource link.
* @memberof ResourceLinkController
*/
@ApiOperation({ summary: 'Update an existing resource link' })
@ApiResponse({
status: HttpStatus.CREATED,
description: 'The record has been successfully edited.'
description: 'The resource link has been successfully updated.'
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Record not found'
description: 'The resource link was not found.'
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: 'Invalid input, The response body may contain clues as to what went wrong'
description: 'Invalid input. The response body may contain details about what went wrong.'
})
@HttpCode(HttpStatus.ACCEPTED)
@Put(':id')
Expand All @@ -105,18 +143,27 @@ export class ResourceLinkController extends CrudController<ResourceLink> {
return await this.commandBus.execute(new ResourceLinkUpdateCommand(id, entity));
}

@ApiOperation({ summary: 'Delete resource' })
/**
* @description Deletes a resource link by its ID.
* This endpoint deletes an existing resource link record from the database.
*
* @param {ID} id - The unique identifier of the resource link to delete.
* @returns {Promise<DeleteResult>} - A promise that resolves to the result of the delete operation.
* @memberof ResourceLinkController
*/
@ApiOperation({ summary: 'Delete a resource link' })
@ApiResponse({
status: HttpStatus.NO_CONTENT,
description: 'The record has been successfully deleted'
description: 'The resource link has been successfully deleted.'
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: 'Record not found'
description: 'The resource link was not found.'
})
@HttpCode(HttpStatus.ACCEPTED)
@Delete('/:id')
async delete(@Param('id', UUIDValidationPipe) id: ID): Promise<DeleteResult> {
// Execute the delete operation and return the result
return await this.resourceLinkService.delete(id);
}
}
39 changes: 29 additions & 10 deletions packages/core/src/lib/resource-link/resource-link.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,42 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import { EntityRepositoryType } from '@mikro-orm/core';
import { JoinColumn, RelationId } from 'typeorm';
import { IsNotEmpty, IsOptional, IsString, IsUrl } from 'class-validator';
import { ID, IResourceLink, IURLMetaData, IUser } from '@gauzy/contracts';
import { IsNotEmpty, IsOptional, IsString, IsUrl, IsUUID } from 'class-validator';
import { ID, IEmployee, IResourceLink, IURLMetaData } from '@gauzy/contracts';
import { isBetterSqlite3, isSqlite } from '@gauzy/config';
import { BasePerEntityType, User } from '../core/entities/internal';
import { BasePerEntityType, Employee } from '../core/entities/internal';
import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity';
import { MikroOrmResourceLinkRepository } from './repository/mikro-orm-resource-link.repository';

@MultiORMEntity('resource_link', { mikroOrmRepository: () => MikroOrmResourceLinkRepository })
export class ResourceLink extends BasePerEntityType implements IResourceLink {
[EntityRepositoryType]?: MikroOrmResourceLinkRepository;

@ApiProperty({ type: () => String })
/**
* The title of the resource link.
* This property holds a brief and descriptive title representing the resource.
*/
@ApiProperty({ type: String })
@IsNotEmpty()
@IsString()
@MultiORMColumn()
title: string;

@ApiProperty({ type: () => String })
/**
* The URL of the resource.
* This property stores the link to the resource associated with the entry.
*/
@ApiProperty({ type: String })
@IsNotEmpty()
@IsUrl()
@MultiORMColumn({ type: 'text' })
url: string;

/**
* Metadata associated with the resource.
* This property stores additional data that can vary in type depending on the database.
* For SQLite, it's stored as a text string, otherwise as a JSON object.
*/
@ApiPropertyOptional({ type: () => (isSqlite() || isBetterSqlite3() ? String : Object) })
@IsOptional()
@MultiORMColumn({ nullable: true, type: isSqlite() || isBetterSqlite3() ? 'text' : 'json' })
Expand All @@ -36,20 +49,26 @@ export class ResourceLink extends BasePerEntityType implements IResourceLink {
|--------------------------------------------------------------------------
*/
/**
* User Author of the Resource Link
* Resource Link Author (Employee).
*/
@MultiORMManyToOne(() => User, {
@MultiORMManyToOne(() => Employee, {
/** Indicates if relation column value can be nullable or not. */
nullable: true,

/** Database cascade action on delete. */
onDelete: 'CASCADE'
})
@JoinColumn()
creator?: IUser;
employee?: IEmployee;

@RelationId((it: ResourceLink) => it.creator)
/**
* Resource Link Author ID.
*/
@ApiPropertyOptional({ type: () => String })
@IsOptional()
@IsUUID()
@RelationId((it: ResourceLink) => it.employee)
@ColumnIndex()
@MultiORMColumn({ nullable: true, relationId: true })
creatorId?: ID;
employeeId?: ID;
}
68 changes: 39 additions & 29 deletions packages/core/src/lib/resource-link/resource-link.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ import {
ActorTypeEnum,
ActionTypeEnum
} from '@gauzy/contracts';
import { TenantAwareCrudService } from './../core/crud';
import { RequestContext } from '../core/context';
import { UserService } from '../user/user.service';
import { TenantAwareCrudService } from './../core/crud/tenant-aware-crud.service';
import { RequestContext } from '../core/context/request-context';
import { EmployeeService } from '../employee/employee.service';
import { ActivityLogService } from '../activity-log/activity-log.service';
import { ResourceLink } from './resource-link.entity';
import { TypeOrmResourceLinkRepository } from './repository/type-orm-resource-link.repository';
Expand All @@ -22,39 +22,44 @@ export class ResourceLinkService extends TenantAwareCrudService<ResourceLink> {
constructor(
readonly typeOrmResourceLinkRepository: TypeOrmResourceLinkRepository,
readonly mikroOrmResourceLinkRepository: MikroOrmResourceLinkRepository,
private readonly userService: UserService,
private readonly activityLogService: ActivityLogService
private readonly _employeeService: EmployeeService,
private readonly _activityLogService: ActivityLogService
) {
super(typeOrmResourceLinkRepository, mikroOrmResourceLinkRepository);
}

/**
* @description Create Resource Link
* @param {IResourceLinkCreateInput} input - Data to creating resource link
* @returns A promise that resolves to the created resource link
* @description Create a new Resource Link
* @param {IResourceLinkCreateInput} input - The data required to create a resource link.
* @returns A promise that resolves to the created resource link entity.
* @memberof ResourceLinkService
*/
async create(input: IResourceLinkCreateInput): Promise<IResourceLink> {
try {
const userId = RequestContext.currentUserId();
const tenantId = RequestContext.currentTenantId();
// Retrieve the tenantId from the request context or fall back to input value
const tenantId = RequestContext.currentTenantId() ?? input.tenantId;

// Retrieve the employeeId from the request context or fall back to input value
const employeeId = RequestContext.currentEmployeeId() ?? input.employeeId;

// Destructure the input data to use in entity creation
const { ...entity } = input;

// Employee existence validation
const user = await this.userService.findOneByIdString(userId);
if (!user) {
throw new NotFoundException('User not found');
// Validate that the employee exists.
const employee = await this._employeeService.findOneByIdString(employeeId);
if (!employee) {
throw new NotFoundException(`Employee with id ${employeeId} not found`);
}

// return created resource link
// Create and return the resource link, passing the necessary entity data
const resourceLink = await super.create({
...entity,
tenantId,
creatorId: user.id
employeeId,
tenantId // Ensure tenantId is included in the entity
});

// Generate the activity log
this.activityLogService.logActivity<ResourceLink>(
this._activityLogService.logActivity<ResourceLink>(
BaseEntityEnum.ResourceLink,
ActionTypeEnum.Created,
ActorTypeEnum.User,
Expand All @@ -67,33 +72,37 @@ export class ResourceLinkService extends TenantAwareCrudService<ResourceLink> {

return resourceLink;
} catch (error) {
console.log(error); // Debug Logging
throw new BadRequestException('Resource Link creation failed', error);
console.log(`Error creating resource link: ${error.message}`, error);
throw new BadRequestException('Error creating resource link', error);
}
}

/**
* @description Update Resource Link
* @param {IResourceLinkUpdateInput} input - Data to update Resource Link
* @returns A promise that resolves to the updated resource link OR Update result
* @description Update an existing Resource Link
* @param {ID} id - The ID of the resource link to update.
* @param {IResourceLinkUpdateInput} input - The data to update the resource link.
* @returns A promise that resolves to the updated resource link entity, or an update result.
* @memberof ResourceLinkService
*/
async update(id: ID, input: IResourceLinkUpdateInput): Promise<IResourceLink | UpdateResult> {
try {
// Retrieve the existing resource link by ID
const resourceLink = await this.findOneByIdString(id);

if (!resourceLink) {
// If the resource link is not found, throw an exception
throw new BadRequestException('Resource Link not found');
}

// Perform the update by creating a new resource link with the updated data
const updatedResourceLink = await super.create({
...input,
id
id // Ensure the ID is passed along with the updated data
});

// Generate the activity log
// Generate the activity log for the update action
const { organizationId, tenantId } = updatedResourceLink;
this.activityLogService.logActivity<ResourceLink>(
this._activityLogService.logActivity<ResourceLink>(
BaseEntityEnum.ResourceLink,
ActionTypeEnum.Updated,
ActorTypeEnum.User,
Expand All @@ -106,11 +115,12 @@ export class ResourceLinkService extends TenantAwareCrudService<ResourceLink> {
input
);

// return updated Resource Link
// Return the updated resource link entity or update result
return updatedResourceLink;
} catch (error) {
console.log(error); // Debug Logging
throw new BadRequestException('Resource Link update failed', error);
// Handle any errors appropriately
console.log(`An error occurred while updating the resource link: ${error.message}`, error);
throw new BadRequestException('An error occurred while updating the resource link', error);
}
}
}
Loading