From bf5db81c476291c12441307d765e8d771094ab14 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Fri, 3 Jan 2025 17:26:24 +0200 Subject: [PATCH 1/5] feat(tag-type): implement backend logic and entity creation for Tag Type management --- packages/contracts/src/lib/tag.model.ts | 8 +++ packages/core/src/index.ts | 1 + .../core/src/lib/core/entities/internal.ts | 1 + .../src/lib/tag-type/default-tag-types.ts | 40 ++++++++++++ packages/core/src/lib/tag-type/index.ts | 2 + .../mikro-orm-tag-type.repository.ts | 4 ++ .../type-orm-tag-type.repository.ts | 11 ++++ .../src/lib/tag-type/tag-type.controller.ts | 61 +++++++++++++++++++ .../core/src/lib/tag-type/tag-type.entity.ts | 25 ++++++++ .../core/src/lib/tag-type/tag-type.module.ts | 23 +++++++ .../core/src/lib/tag-type/tag-type.seed.ts | 24 ++++++++ .../core/src/lib/tag-type/tag-type.service.ts | 18 ++++++ packages/core/src/lib/tags/tag.entity.ts | 20 +++++- 13 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/lib/tag-type/default-tag-types.ts create mode 100644 packages/core/src/lib/tag-type/index.ts create mode 100644 packages/core/src/lib/tag-type/repository/mikro-orm-tag-type.repository.ts create mode 100644 packages/core/src/lib/tag-type/repository/type-orm-tag-type.repository.ts create mode 100644 packages/core/src/lib/tag-type/tag-type.controller.ts create mode 100644 packages/core/src/lib/tag-type/tag-type.entity.ts create mode 100644 packages/core/src/lib/tag-type/tag-type.module.ts create mode 100644 packages/core/src/lib/tag-type/tag-type.seed.ts create mode 100644 packages/core/src/lib/tag-type/tag-type.service.ts diff --git a/packages/contracts/src/lib/tag.model.ts b/packages/contracts/src/lib/tag.model.ts index ae8d55dba64..7bb548cf575 100644 --- a/packages/contracts/src/lib/tag.model.ts +++ b/packages/contracts/src/lib/tag.model.ts @@ -12,6 +12,8 @@ export interface ITag extends IBasePerTenantAndOrganizationEntityModel, IRelatio icon?: string; description?: string; isSystem?: boolean; + tagTypeId?: string; + tagType?: ITagType; } export interface ITagFindInput extends IBasePerTenantAndOrganizationEntityModel, Pick { @@ -20,10 +22,16 @@ export interface ITagFindInput extends IBasePerTenantAndOrganizationEntityModel, textColor?: string; description?: string; isSystem?: boolean; + tagTypeId?: string; } export interface ITagCreateInput extends ITag {} +export interface ITagType { + type: string; + tags?: ITag[]; +} + export interface ITagUpdateInput extends Partial { id?: string; } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index d13742879ba..c23f681e963 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -100,3 +100,4 @@ export { ExpenseCategoryFirstOrCreateCommand } from './lib/expense-categories'; export { TagModule, TagService, Taggable, AutomationLabelSyncCommand, RelationalTagDTO } from './lib/tags'; +export { TagTypeModule, TagTypeService } from './lib/tag-type'; diff --git a/packages/core/src/lib/core/entities/internal.ts b/packages/core/src/lib/core/entities/internal.ts index 0887b83bf19..0f9272b6bdb 100644 --- a/packages/core/src/lib/core/entities/internal.ts +++ b/packages/core/src/lib/core/entities/internal.ts @@ -131,6 +131,7 @@ export * from '../../role/role.entity'; export * from '../../skills/skill.entity'; export * from '../../subscription/subscription.entity'; export * from '../../tags/tag.entity'; +export * from '../../tag-type/tag-type.entity'; export * from '../../tasks/daily-plan/daily-plan.entity'; export * from '../../tasks/estimation/task-estimation.entity'; export * from '../../tasks/issue-type/issue-type.entity'; diff --git a/packages/core/src/lib/tag-type/default-tag-types.ts b/packages/core/src/lib/tag-type/default-tag-types.ts new file mode 100644 index 00000000000..bb4702421be --- /dev/null +++ b/packages/core/src/lib/tag-type/default-tag-types.ts @@ -0,0 +1,40 @@ +import { ITagType } from '@gauzy/contracts'; + +export const DEFAULT_TAG_TYPES: ITagType[] = [ + { + type: 'Equipment' + }, + { + type: 'Income' + }, + { + type: 'Invoice' + }, + { + type: 'Payment' + }, + { + type: 'Task' + }, + { + type: 'Proposals' + }, + { + type: 'Organization Contact' + }, + { + type: 'Employee Level' + }, + { + type: 'Organization Department' + }, + { + type: 'Warehouse' + }, + { + type: 'Employee' + }, + { + type: 'User' + } +]; diff --git a/packages/core/src/lib/tag-type/index.ts b/packages/core/src/lib/tag-type/index.ts new file mode 100644 index 00000000000..1773462762c --- /dev/null +++ b/packages/core/src/lib/tag-type/index.ts @@ -0,0 +1,2 @@ +export * from './tag-type.module'; +export * from './tag-type.service'; diff --git a/packages/core/src/lib/tag-type/repository/mikro-orm-tag-type.repository.ts b/packages/core/src/lib/tag-type/repository/mikro-orm-tag-type.repository.ts new file mode 100644 index 00000000000..936d2eb0f95 --- /dev/null +++ b/packages/core/src/lib/tag-type/repository/mikro-orm-tag-type.repository.ts @@ -0,0 +1,4 @@ +import { MikroOrmBaseEntityRepository } from '../../core/repository/mikro-orm-base-entity.repository'; +import { TagType } from '../tag-type.entity'; + +export class MikroOrmTagTypeRepository extends MikroOrmBaseEntityRepository {} diff --git a/packages/core/src/lib/tag-type/repository/type-orm-tag-type.repository.ts b/packages/core/src/lib/tag-type/repository/type-orm-tag-type.repository.ts new file mode 100644 index 00000000000..cc39dc5060c --- /dev/null +++ b/packages/core/src/lib/tag-type/repository/type-orm-tag-type.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { TagType } from '../tag-type.entity'; + +@Injectable() +export class TypeOrmTagTypeRepository extends Repository { + constructor(@InjectRepository(TagType) readonly repository: Repository) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/lib/tag-type/tag-type.controller.ts b/packages/core/src/lib/tag-type/tag-type.controller.ts new file mode 100644 index 00000000000..5564188ad9f --- /dev/null +++ b/packages/core/src/lib/tag-type/tag-type.controller.ts @@ -0,0 +1,61 @@ +import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; +import { Controller, HttpStatus, Get, Query, UseGuards, ValidationPipe } from '@nestjs/common'; +import { FindOptionsWhere } from 'typeorm'; +import { IPagination } from '@gauzy/contracts'; +import { TagType } from './tag-type.entity'; +import { TagTypeService } from './tag-type.service'; +import { PermissionGuard, TenantPermissionGuard } from '../shared'; +import { CrudController, PaginationParams } from '../core'; + +@ApiTags('TagTypes') +@UseGuards(TenantPermissionGuard, PermissionGuard) +@Controller() +export class TagTypeController extends CrudController { + constructor(private readonly tagTypesService: TagTypeService) { + super(tagTypesService); + } + + /** + * GET tag types count + * + * @param data + * @returns + */ + @ApiOperation({ summary: 'Find Tag Types Count ' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Count Tag Types', + type: TagType + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @Get('count') + async getCount(@Query() options: FindOptionsWhere): Promise { + return await this.tagTypesService.countBy(options); + } + + /** + * GET all tag types + * + * @param options + * @returns + */ + @ApiOperation({ + summary: 'Find all tag types.' + }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Found tag types.', + type: TagType + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Record not found' + }) + @Get() + async findAll(@Query(new ValidationPipe()) options: PaginationParams): Promise> { + return await this.tagTypesService.findAll(options); + } +} diff --git a/packages/core/src/lib/tag-type/tag-type.entity.ts b/packages/core/src/lib/tag-type/tag-type.entity.ts new file mode 100644 index 00000000000..e31fdbcee71 --- /dev/null +++ b/packages/core/src/lib/tag-type/tag-type.entity.ts @@ -0,0 +1,25 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; +import { MultiORMColumn, MultiORMEntity, MultiORMOneToMany } from './../core/decorators/entity'; +import { MikroOrmTagTypeRepository } from './repository/mikro-orm-tag-type.repository'; +import { ITagType } from '@gauzy/contracts'; + +@MultiORMEntity('tag_type', { mikroOrmRepository: () => MikroOrmTagTypeRepository }) +export class TagType extends TenantOrganizationBaseEntity implements ITagType { + @ApiProperty({ type: () => String }) + @MultiORMColumn() + type: string; + + /* + |-------------------------------------------------------------------------- + | @OneToMany + |-------------------------------------------------------------------------- + */ + + /** + * tags + */ + @ApiProperty({ type: () => Tag, isArray: true }) + @MultiORMOneToMany(() => Tag, (tag) => tag.tagType) + tags?: Tag[]; +} diff --git a/packages/core/src/lib/tag-type/tag-type.module.ts b/packages/core/src/lib/tag-type/tag-type.module.ts new file mode 100644 index 00000000000..ea2c7de7858 --- /dev/null +++ b/packages/core/src/lib/tag-type/tag-type.module.ts @@ -0,0 +1,23 @@ +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CqrsModule } from '@nestjs/cqrs'; +import { Module } from '@nestjs/common'; +import { RouterModule } from '@nestjs/core'; +import { MikroOrmModule } from '@mikro-orm/nestjs'; +import { RolePermissionModule } from '../role-permission/role-permission.module'; +import { TagType } from './tag-type.entity'; +import { TagTypeService } from './tag-type.service'; +import { TagTypeController } from './tag-type.controller'; + +@Module({ + imports: [ + RouterModule.register([{ path: '/tag-types', module: TagTypeModule }]), + TypeOrmModule.forFeature([TagType]), + MikroOrmModule.forFeature([TagType]), + RolePermissionModule, + CqrsModule + ], + controllers: [TagTypeController], + providers: [TagTypeService], + exports: [TypeOrmModule, MikroOrmModule, TagTypeService] +}) +export class TagTypeModule {} diff --git a/packages/core/src/lib/tag-type/tag-type.seed.ts b/packages/core/src/lib/tag-type/tag-type.seed.ts new file mode 100644 index 00000000000..6b25ee99510 --- /dev/null +++ b/packages/core/src/lib/tag-type/tag-type.seed.ts @@ -0,0 +1,24 @@ +import { DataSource } from 'typeorm'; +import { IOrganization, ITagType, ITenant } from '@gauzy/contracts'; +import { TagType } from './tag-type.entity'; +import { DEFAULT_TAG_TYPES } from './default-tag-types'; + +export const createTagTypes = async ( + dataSource: DataSource, + tenant: ITenant, + organizations: IOrganization[] +): Promise => { + const tagTypes: TagType[] = []; + DEFAULT_TAG_TYPES.forEach(({ type }) => { + for (const organization of organizations) { + const entity = new TagType(); + entity.type = type; + entity.organization = organization; + entity.tenant = tenant; + tagTypes.push(entity); + } + }); + return insertLevels(dataSource, tagTypes); +}; + +const insertLevels = async (dataSource: DataSource, tagTypes: TagType[]) => await dataSource.manager.save(tagTypes); diff --git a/packages/core/src/lib/tag-type/tag-type.service.ts b/packages/core/src/lib/tag-type/tag-type.service.ts new file mode 100644 index 00000000000..becaf00e3fd --- /dev/null +++ b/packages/core/src/lib/tag-type/tag-type.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TenantAwareCrudService } from '../core/crud'; +import { TagType } from './tag-type.entity'; +import { MikroOrmTagTypeRepository } from './repository/mikro-orm-tag-type.repository'; +import { TypeOrmTagTypeRepository } from './repository/type-orm-tag-type.repository'; + +@Injectable() +export class TagTypeService extends TenantAwareCrudService { + constructor( + @InjectRepository(TagType) + typeOrmProductTypeRepository: TypeOrmTagTypeRepository, + + mikroOrmProductTypeRepository: MikroOrmTagTypeRepository + ) { + super(typeOrmProductTypeRepository, mikroOrmProductTypeRepository); + } +} diff --git a/packages/core/src/lib/tags/tag.entity.ts b/packages/core/src/lib/tags/tag.entity.ts index 78277984c79..c97f68150ed 100644 --- a/packages/core/src/lib/tags/tag.entity.ts +++ b/packages/core/src/lib/tags/tag.entity.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { RelationId } from 'typeorm'; +import { JoinColumn, RelationId } from 'typeorm'; // eslint-disable-next-line @typescript-eslint/no-unused-vars import { EntityRepositoryType } from '@mikro-orm/core'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; @@ -74,6 +74,7 @@ import { TypeOrmTagEntityCustomFields } from '../core/entities/custom-entity-fields/tag'; import { MikroOrmTagRepository } from './repository/mikro-orm-tag.repository'; +import { TagType } from '../tag-type/tag-type.entity'; @MultiORMEntity('tag', { mikroOrmRepository: () => MikroOrmTagRepository }) export class Tag extends TenantOrganizationBaseEntity implements ITag { @@ -115,6 +116,23 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @VirtualMultiOrmColumn() fullIconUrl?: string; + /** + * TagType + */ + @ApiProperty({ type: () => TagType }) + @MultiORMManyToOne(() => TagType, (tagType) => tagType.tags, { + onDelete: 'SET NULL' + }) + @JoinColumn() + tagType?: TagType; + + @ApiProperty({ type: () => String }) + @RelationId((it: Tag) => it.tagType) + @IsString() + @ColumnIndex() + @MultiORMColumn({ nullable: true, relationId: true }) + tagTypeId?: string; + /* |-------------------------------------------------------------------------- | @ManyToOne From f27d7d481924bf29f3f235d08fc224ed83ebf6da Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Sat, 4 Jan 2025 09:38:44 +0200 Subject: [PATCH 2/5] Export new entity --- packages/core/src/lib/core/entities/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/lib/core/entities/index.ts b/packages/core/src/lib/core/entities/index.ts index 52c2b94f294..6af5502d6c8 100644 --- a/packages/core/src/lib/core/entities/index.ts +++ b/packages/core/src/lib/core/entities/index.ts @@ -130,6 +130,7 @@ import { SocialAccount, Subscription, Tag, + TagType, Task, TaskEstimation, TaskLinkedIssue, @@ -286,6 +287,7 @@ export const coreEntities = [ SocialAccount, Subscription, Tag, + TagType, Task, TaskEstimation, TaskLinkedIssue, From df66212b7ce11e2ca59ecab83e799a60dbbb1739 Mon Sep 17 00:00:00 2001 From: samuelmbabhazi Date: Mon, 6 Jan 2025 08:34:57 +0200 Subject: [PATCH 3/5] renaming injected repository --- packages/core/src/lib/tag-type/tag-type.service.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core/src/lib/tag-type/tag-type.service.ts b/packages/core/src/lib/tag-type/tag-type.service.ts index becaf00e3fd..b5c598a6688 100644 --- a/packages/core/src/lib/tag-type/tag-type.service.ts +++ b/packages/core/src/lib/tag-type/tag-type.service.ts @@ -9,10 +9,10 @@ import { TypeOrmTagTypeRepository } from './repository/type-orm-tag-type.reposit export class TagTypeService extends TenantAwareCrudService { constructor( @InjectRepository(TagType) - typeOrmProductTypeRepository: TypeOrmTagTypeRepository, + typeOrmTagTypeRepository: TypeOrmTagTypeRepository, - mikroOrmProductTypeRepository: MikroOrmTagTypeRepository + mikroOrmTagTypeRepository: MikroOrmTagTypeRepository ) { - super(typeOrmProductTypeRepository, mikroOrmProductTypeRepository); + super(typeOrmTagTypeRepository, mikroOrmTagTypeRepository); } } From 23ccf2cec6e28ad3b62eacb0c0698d0fda7fd3f1 Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Mon, 6 Jan 2025 14:48:15 +0530 Subject: [PATCH 4/5] refactor: tag type module/entity --- packages/contracts/src/lib/tag.model.ts | 8 +-- .../src/lib/tag-type/default-tag-types.ts | 67 +++++++++---------- .../src/lib/tag-type/tag-type.controller.ts | 6 +- .../core/src/lib/tag-type/tag-type.entity.ts | 8 ++- .../core/src/lib/tag-type/tag-type.module.ts | 15 ++--- .../core/src/lib/tag-type/tag-type.seed.ts | 66 ++++++++++++++++-- .../core/src/lib/tag-type/tag-type.service.ts | 3 - packages/core/src/lib/tags/tag.entity.ts | 12 ++-- 8 files changed, 118 insertions(+), 67 deletions(-) diff --git a/packages/contracts/src/lib/tag.model.ts b/packages/contracts/src/lib/tag.model.ts index 7bb548cf575..da011954e1d 100644 --- a/packages/contracts/src/lib/tag.model.ts +++ b/packages/contracts/src/lib/tag.model.ts @@ -1,5 +1,5 @@ import { IRelationalOrganizationTeam } from './organization-team.model'; -import { IBasePerTenantAndOrganizationEntityModel } from './base-entity.model'; +import { IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model'; export interface ITaggable { tags?: ITag[]; @@ -12,7 +12,7 @@ export interface ITag extends IBasePerTenantAndOrganizationEntityModel, IRelatio icon?: string; description?: string; isSystem?: boolean; - tagTypeId?: string; + tagTypeId?: ID; tagType?: ITagType; } @@ -22,7 +22,7 @@ export interface ITagFindInput extends IBasePerTenantAndOrganizationEntityModel, textColor?: string; description?: string; isSystem?: boolean; - tagTypeId?: string; + tagTypeId?: ID; } export interface ITagCreateInput extends ITag {} @@ -33,7 +33,7 @@ export interface ITagType { } export interface ITagUpdateInput extends Partial { - id?: string; + id?: ID; } /** diff --git a/packages/core/src/lib/tag-type/default-tag-types.ts b/packages/core/src/lib/tag-type/default-tag-types.ts index bb4702421be..1dd54b40687 100644 --- a/packages/core/src/lib/tag-type/default-tag-types.ts +++ b/packages/core/src/lib/tag-type/default-tag-types.ts @@ -1,40 +1,35 @@ import { ITagType } from '@gauzy/contracts'; +/** + * Represents the default tag types used throughout the application. + * Each tag type corresponds to a specific category or grouping. + * + * @constant DEFAULT_TAG_TYPES + * @type {ITagType[]} + * + * @description + * This constant defines an array of objects that represent default tag types. + * Each object contains a `type` property, which is a string describing the tag type. + * These tags can be used to classify various entities such as equipment, income, + * invoices, and more. + * + * Example Usage: + * ```typescript + * console.log(DEFAULT_TAG_TYPES); + * ``` + * + */ export const DEFAULT_TAG_TYPES: ITagType[] = [ - { - type: 'Equipment' - }, - { - type: 'Income' - }, - { - type: 'Invoice' - }, - { - type: 'Payment' - }, - { - type: 'Task' - }, - { - type: 'Proposals' - }, - { - type: 'Organization Contact' - }, - { - type: 'Employee Level' - }, - { - type: 'Organization Department' - }, - { - type: 'Warehouse' - }, - { - type: 'Employee' - }, - { - type: 'User' - } + { type: 'Equipment' }, + { type: 'Income' }, + { type: 'Invoice' }, + { type: 'Payment' }, + { type: 'Task' }, + { type: 'Proposals' }, + { type: 'Organization Contact' }, + { type: 'Employee Level' }, + { type: 'Organization Department' }, + { type: 'Warehouse' }, + { type: 'Employee' }, + { type: 'User' } ]; diff --git a/packages/core/src/lib/tag-type/tag-type.controller.ts b/packages/core/src/lib/tag-type/tag-type.controller.ts index 5564188ad9f..2e0a082904e 100644 --- a/packages/core/src/lib/tag-type/tag-type.controller.ts +++ b/packages/core/src/lib/tag-type/tag-type.controller.ts @@ -2,14 +2,14 @@ import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { Controller, HttpStatus, Get, Query, UseGuards, ValidationPipe } from '@nestjs/common'; import { FindOptionsWhere } from 'typeorm'; import { IPagination } from '@gauzy/contracts'; +import { CrudController, PaginationParams } from '../core/crud'; +import { PermissionGuard, TenantPermissionGuard } from '../shared/guards'; import { TagType } from './tag-type.entity'; import { TagTypeService } from './tag-type.service'; -import { PermissionGuard, TenantPermissionGuard } from '../shared'; -import { CrudController, PaginationParams } from '../core'; @ApiTags('TagTypes') @UseGuards(TenantPermissionGuard, PermissionGuard) -@Controller() +@Controller('/tag-types') export class TagTypeController extends CrudController { constructor(private readonly tagTypesService: TagTypeService) { super(tagTypesService); diff --git a/packages/core/src/lib/tag-type/tag-type.entity.ts b/packages/core/src/lib/tag-type/tag-type.entity.ts index e31fdbcee71..e50a2c6635f 100644 --- a/packages/core/src/lib/tag-type/tag-type.entity.ts +++ b/packages/core/src/lib/tag-type/tag-type.entity.ts @@ -1,12 +1,15 @@ import { ApiProperty } from '@nestjs/swagger'; +import { ITag, ITagType } from '@gauzy/contracts'; +import { IsNotEmpty, IsString } from 'class-validator'; import { Tag, TenantOrganizationBaseEntity } from '../core/entities/internal'; import { MultiORMColumn, MultiORMEntity, MultiORMOneToMany } from './../core/decorators/entity'; import { MikroOrmTagTypeRepository } from './repository/mikro-orm-tag-type.repository'; -import { ITagType } from '@gauzy/contracts'; @MultiORMEntity('tag_type', { mikroOrmRepository: () => MikroOrmTagTypeRepository }) export class TagType extends TenantOrganizationBaseEntity implements ITagType { @ApiProperty({ type: () => String }) + @IsNotEmpty() + @IsString() @MultiORMColumn() type: string; @@ -15,11 +18,10 @@ export class TagType extends TenantOrganizationBaseEntity implements ITagType { | @OneToMany |-------------------------------------------------------------------------- */ - /** * tags */ @ApiProperty({ type: () => Tag, isArray: true }) @MultiORMOneToMany(() => Tag, (tag) => tag.tagType) - tags?: Tag[]; + tags?: ITag[]; } diff --git a/packages/core/src/lib/tag-type/tag-type.module.ts b/packages/core/src/lib/tag-type/tag-type.module.ts index ea2c7de7858..cc1579c276a 100644 --- a/packages/core/src/lib/tag-type/tag-type.module.ts +++ b/packages/core/src/lib/tag-type/tag-type.module.ts @@ -1,23 +1,22 @@ -import { TypeOrmModule } from '@nestjs/typeorm'; -import { CqrsModule } from '@nestjs/cqrs'; import { Module } from '@nestjs/common'; -import { RouterModule } from '@nestjs/core'; +import { CqrsModule } from '@nestjs/cqrs'; +import { TypeOrmModule } from '@nestjs/typeorm'; import { MikroOrmModule } from '@mikro-orm/nestjs'; import { RolePermissionModule } from '../role-permission/role-permission.module'; import { TagType } from './tag-type.entity'; import { TagTypeService } from './tag-type.service'; import { TagTypeController } from './tag-type.controller'; +import { TypeOrmTagTypeRepository } from './repository/type-orm-tag-type.repository'; @Module({ imports: [ - RouterModule.register([{ path: '/tag-types', module: TagTypeModule }]), + CqrsModule, TypeOrmModule.forFeature([TagType]), MikroOrmModule.forFeature([TagType]), - RolePermissionModule, - CqrsModule + RolePermissionModule ], controllers: [TagTypeController], - providers: [TagTypeService], - exports: [TypeOrmModule, MikroOrmModule, TagTypeService] + providers: [TagTypeService, TypeOrmTagTypeRepository], + exports: [TagTypeService] }) export class TagTypeModule {} diff --git a/packages/core/src/lib/tag-type/tag-type.seed.ts b/packages/core/src/lib/tag-type/tag-type.seed.ts index 6b25ee99510..ca8e116fe1d 100644 --- a/packages/core/src/lib/tag-type/tag-type.seed.ts +++ b/packages/core/src/lib/tag-type/tag-type.seed.ts @@ -3,22 +3,78 @@ import { IOrganization, ITagType, ITenant } from '@gauzy/contracts'; import { TagType } from './tag-type.entity'; import { DEFAULT_TAG_TYPES } from './default-tag-types'; +/** + * Creates and inserts tag types into the database for a specified tenant and organizations. + * + * @function createTagTypes + * @async + * @param {DataSource} dataSource - The TypeORM `DataSource` instance used for database operations. + * @param {ITenant} tenant - The tenant for which the tag types are being created. + * @param {IOrganization[]} organizations - An array of organizations associated with the tag types. + * @returns {Promise} - A promise that resolves to the array of created and inserted `ITagType` entities. + * + * @description + * This function iterates over the predefined `DEFAULT_TAG_TYPES` and creates `TagType` entities + * for each organization provided. It assigns the `type` from `DEFAULT_TAG_TYPES`, associates the + * organization and tenant, and saves the resulting entities into the database. + * + * @example + * const organizations = [ + * { id: 'org1', name: 'Org 1' }, + * { id: 'org2', name: 'Org 2' }, + * ]; + * + * const tenant = { id: 'tenant1', name: 'Tenant 1' }; + * + * const createdTags = await createTagTypes(dataSource, tenant, organizations); + * console.log(createdTags); + * + * @throws Will throw an error if the database save operation fails. + */ export const createTagTypes = async ( dataSource: DataSource, tenant: ITenant, organizations: IOrganization[] ): Promise => { + // Array to store the new tag type entities const tagTypes: TagType[] = []; + + // Iterate over the predefined default tag types DEFAULT_TAG_TYPES.forEach(({ type }) => { + // Create a tag type for each organization for (const organization of organizations) { const entity = new TagType(); - entity.type = type; - entity.organization = organization; - entity.tenant = tenant; + entity.type = type; // Assign type from default list + entity.organization = organization; // Associate organization + entity.tenant = tenant; // Associate tenant tagTypes.push(entity); } }); - return insertLevels(dataSource, tagTypes); + + // Save the created tag types into the database + return insertTagTypes(dataSource, tagTypes); }; -const insertLevels = async (dataSource: DataSource, tagTypes: TagType[]) => await dataSource.manager.save(tagTypes); +/** + * Inserts an array of tag types into the database. + * + * @function insertTagTypes + * @async + * @param {DataSource} dataSource - The TypeORM `DataSource` instance used to interact with the database. + * @param {TagType[]} tagTypes - An array of `TagType` entities to be saved into the database. + * @returns {Promise} - A promise that resolves with the array of saved `TagType` entities. + * + * @example + * // Example usage: + * const tagTypes = [ + * { type: 'Equipment' }, + * { type: 'Income' }, + * ]; + * + * await insertTagTypes(dataSource, tagTypes); + * + * @throws Will throw an error if the database save operation fails. + */ +const insertTagTypes = async (dataSource: DataSource, tagTypes: TagType[]): Promise => { + return await dataSource.manager.save(tagTypes); +}; diff --git a/packages/core/src/lib/tag-type/tag-type.service.ts b/packages/core/src/lib/tag-type/tag-type.service.ts index b5c598a6688..e7a68fc257e 100644 --- a/packages/core/src/lib/tag-type/tag-type.service.ts +++ b/packages/core/src/lib/tag-type/tag-type.service.ts @@ -1,5 +1,4 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { TenantAwareCrudService } from '../core/crud'; import { TagType } from './tag-type.entity'; import { MikroOrmTagTypeRepository } from './repository/mikro-orm-tag-type.repository'; @@ -8,9 +7,7 @@ import { TypeOrmTagTypeRepository } from './repository/type-orm-tag-type.reposit @Injectable() export class TagTypeService extends TenantAwareCrudService { constructor( - @InjectRepository(TagType) typeOrmTagTypeRepository: TypeOrmTagTypeRepository, - mikroOrmTagTypeRepository: MikroOrmTagTypeRepository ) { super(typeOrmTagTypeRepository, mikroOrmTagTypeRepository); diff --git a/packages/core/src/lib/tags/tag.entity.ts b/packages/core/src/lib/tags/tag.entity.ts index c97f68150ed..52e2fa65fc1 100644 --- a/packages/core/src/lib/tags/tag.entity.ts +++ b/packages/core/src/lib/tags/tag.entity.ts @@ -5,6 +5,7 @@ import { EntityRepositoryType } from '@mikro-orm/core'; import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { ICandidate, + ID, IEmployee, IEmployeeLevel, IEquipment, @@ -27,6 +28,7 @@ import { IProduct, IRequestApproval, ITag, + ITagType, ITask, IUser, IWarehouse @@ -54,6 +56,7 @@ import { Payment, Product, RequestApproval, + TagType, Task, TenantOrganizationBaseEntity, User, @@ -74,7 +77,6 @@ import { TypeOrmTagEntityCustomFields } from '../core/entities/custom-entity-fields/tag'; import { MikroOrmTagRepository } from './repository/mikro-orm-tag.repository'; -import { TagType } from '../tag-type/tag-type.entity'; @MultiORMEntity('tag', { mikroOrmRepository: () => MikroOrmTagRepository }) export class Tag extends TenantOrganizationBaseEntity implements ITag { @@ -120,18 +122,18 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { * TagType */ @ApiProperty({ type: () => TagType }) - @MultiORMManyToOne(() => TagType, (tagType) => tagType.tags, { + @MultiORMManyToOne(() => TagType, (it) => it.tags, { onDelete: 'SET NULL' }) @JoinColumn() - tagType?: TagType; + tagType?: ITagType; @ApiProperty({ type: () => String }) @RelationId((it: Tag) => it.tagType) @IsString() @ColumnIndex() @MultiORMColumn({ nullable: true, relationId: true }) - tagTypeId?: string; + tagTypeId?: ID; /* |-------------------------------------------------------------------------- @@ -157,7 +159,7 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @RelationId((it: Tag) => it.organizationTeam) @ColumnIndex() @MultiORMColumn({ nullable: true, relationId: true }) - organizationTeamId?: IOrganizationTeam['id']; + organizationTeamId?: ID; /* |-------------------------------------------------------------------------- From 2883b91abe5986d5a1f8e2de312f4f920a382bdb Mon Sep 17 00:00:00 2001 From: "Rahul R." Date: Mon, 6 Jan 2025 15:25:25 +0530 Subject: [PATCH 5/5] feat(migration): create tag_type [table] for DBs --- .../1736155704913-CreateTagTypeTable.ts | 235 ++++++++++++++++++ packages/core/src/lib/tags/tag.entity.ts | 19 +- 2 files changed, 246 insertions(+), 8 deletions(-) create mode 100644 packages/core/src/lib/database/migrations/1736155704913-CreateTagTypeTable.ts diff --git a/packages/core/src/lib/database/migrations/1736155704913-CreateTagTypeTable.ts b/packages/core/src/lib/database/migrations/1736155704913-CreateTagTypeTable.ts new file mode 100644 index 00000000000..0ae7810b4c4 --- /dev/null +++ b/packages/core/src/lib/database/migrations/1736155704913-CreateTagTypeTable.ts @@ -0,0 +1,235 @@ + +import { MigrationInterface, QueryRunner } from "typeorm"; +import * as chalk from 'chalk'; +import { DatabaseTypeEnum } from "@gauzy/config"; + +export class CreateTagTypeTable1736155704913 implements MigrationInterface { + + name = 'CreateTagTypeTable1736155704913'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + console.log(chalk.yellow(this.name + ' start running!')); + + switch (queryRunner.connection.options.type) { + case DatabaseTypeEnum.sqlite: + case DatabaseTypeEnum.betterSqlite3: + await this.sqliteUpQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.postgres: + await this.postgresUpQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.mysql: + await this.mysqlUpQueryRunner(queryRunner); + break; + default: + throw Error(`Unsupported database: ${queryRunner.connection.options.type}`); + } + } + + /** + * Down Migration + * + * @param queryRunner + */ + public async down(queryRunner: QueryRunner): Promise { + switch (queryRunner.connection.options.type) { + case DatabaseTypeEnum.sqlite: + case DatabaseTypeEnum.betterSqlite3: + await this.sqliteDownQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.postgres: + await this.postgresDownQueryRunner(queryRunner); + break; + case DatabaseTypeEnum.mysql: + await this.mysqlDownQueryRunner(queryRunner); + break; + default: + throw Error(`Unsupported database: ${queryRunner.connection.options.type}`); + } + } + + /** + * PostgresDB Up Migration + * + * @param queryRunner + */ + public async postgresUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "tag_type" ("deletedAt" TIMESTAMP, "id" uuid NOT NULL DEFAULT gen_random_uuid(), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "isActive" boolean DEFAULT true, "isArchived" boolean DEFAULT false, "archivedAt" TIMESTAMP, "tenantId" uuid, "organizationId" uuid, "type" character varying NOT NULL, CONSTRAINT "PK_0829ee814cd10d5a337eaa07443" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_c1bef0b8aa28d47b9907667a1f" ON "tag_type" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_e2d517d3e3ff2a873f43367813" ON "tag_type" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_556112dc97b482454726198f25" ON "tag_type" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_dcefa726d2f515583535cb540f" ON "tag_type" ("organizationId") `); + await queryRunner.query(`ALTER TABLE "tag" ADD "tagTypeId" uuid`); + await queryRunner.query(`CREATE INDEX "IDX_1c84215eb01fa457d0beeaee7f" ON "tag" ("tagTypeId") `); + await queryRunner.query(`ALTER TABLE "tag" ADD CONSTRAINT "FK_1c84215eb01fa457d0beeaee7fc" FOREIGN KEY ("tagTypeId") REFERENCES "tag_type"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "tag_type" ADD CONSTRAINT "FK_556112dc97b482454726198f250" FOREIGN KEY ("tenantId") REFERENCES "tenant"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "tag_type" ADD CONSTRAINT "FK_dcefa726d2f515583535cb540f3" FOREIGN KEY ("organizationId") REFERENCES "organization"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "tag_type" DROP CONSTRAINT "FK_dcefa726d2f515583535cb540f3"`); + await queryRunner.query(`ALTER TABLE "tag_type" DROP CONSTRAINT "FK_556112dc97b482454726198f250"`); + await queryRunner.query(`ALTER TABLE "tag" DROP CONSTRAINT "FK_1c84215eb01fa457d0beeaee7fc"`); + await queryRunner.query(`DROP INDEX "public"."IDX_1c84215eb01fa457d0beeaee7f"`); + await queryRunner.query(`ALTER TABLE "tag" DROP COLUMN "tagTypeId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_dcefa726d2f515583535cb540f"`); + await queryRunner.query(`DROP INDEX "public"."IDX_556112dc97b482454726198f25"`); + await queryRunner.query(`DROP INDEX "public"."IDX_e2d517d3e3ff2a873f43367813"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c1bef0b8aa28d47b9907667a1f"`); + await queryRunner.query(`DROP TABLE "tag_type"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "tag_type" ("deletedAt" datetime, "id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "type" varchar NOT NULL)`); + await queryRunner.query(`CREATE INDEX "IDX_c1bef0b8aa28d47b9907667a1f" ON "tag_type" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_e2d517d3e3ff2a873f43367813" ON "tag_type" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_556112dc97b482454726198f25" ON "tag_type" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_dcefa726d2f515583535cb540f" ON "tag_type" ("organizationId") `); + await queryRunner.query(`DROP INDEX "IDX_49746602acc4e5e8721062b69e"`); + await queryRunner.query(`DROP INDEX "IDX_b08dd29fb6a8acdf83c83d8988"`); + await queryRunner.query(`DROP INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9"`); + await queryRunner.query(`DROP INDEX "IDX_1f22c73374bcca1ea84a4dca59"`); + await queryRunner.query(`DROP INDEX "IDX_58876ee26a90170551027459bf"`); + await queryRunner.query(`CREATE TABLE "temporary_tag" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "description" varchar, "color" varchar NOT NULL, "isSystem" boolean NOT NULL DEFAULT (0), "icon" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "textColor" varchar, "fix_relational_custom_fields" boolean, "archivedAt" datetime, "tagTypeId" varchar, CONSTRAINT "FK_49746602acc4e5e8721062b69ec" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_b08dd29fb6a8acdf83c83d8988f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_c2f6bec0b39eaa3a6d90903ae99" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE)`); + await queryRunner.query(`INSERT INTO "temporary_tag"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt" FROM "tag"`); + await queryRunner.query(`DROP TABLE "tag"`); + await queryRunner.query(`ALTER TABLE "temporary_tag" RENAME TO "tag"`); + await queryRunner.query(`CREATE INDEX "IDX_49746602acc4e5e8721062b69e" ON "tag" ("organizationTeamId") `); + await queryRunner.query(`CREATE INDEX "IDX_b08dd29fb6a8acdf83c83d8988" ON "tag" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9" ON "tag" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_1f22c73374bcca1ea84a4dca59" ON "tag" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_58876ee26a90170551027459bf" ON "tag" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_1c84215eb01fa457d0beeaee7f" ON "tag" ("tagTypeId") `); + await queryRunner.query(`DROP INDEX "IDX_49746602acc4e5e8721062b69e"`); + await queryRunner.query(`DROP INDEX "IDX_b08dd29fb6a8acdf83c83d8988"`); + await queryRunner.query(`DROP INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9"`); + await queryRunner.query(`DROP INDEX "IDX_1f22c73374bcca1ea84a4dca59"`); + await queryRunner.query(`DROP INDEX "IDX_58876ee26a90170551027459bf"`); + await queryRunner.query(`DROP INDEX "IDX_1c84215eb01fa457d0beeaee7f"`); + await queryRunner.query(`CREATE TABLE "temporary_tag" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "description" varchar, "color" varchar NOT NULL, "isSystem" boolean NOT NULL DEFAULT (0), "icon" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "textColor" varchar, "fix_relational_custom_fields" boolean, "archivedAt" datetime, "tagTypeId" varchar, CONSTRAINT "FK_49746602acc4e5e8721062b69ec" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_b08dd29fb6a8acdf83c83d8988f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_c2f6bec0b39eaa3a6d90903ae99" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_1c84215eb01fa457d0beeaee7fc" FOREIGN KEY ("tagTypeId") REFERENCES "tag_type" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_tag"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt", "tagTypeId") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt", "tagTypeId" FROM "tag"`); + await queryRunner.query(`DROP TABLE "tag"`); + await queryRunner.query(`ALTER TABLE "temporary_tag" RENAME TO "tag"`); + await queryRunner.query(`CREATE INDEX "IDX_49746602acc4e5e8721062b69e" ON "tag" ("organizationTeamId") `); + await queryRunner.query(`CREATE INDEX "IDX_b08dd29fb6a8acdf83c83d8988" ON "tag" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9" ON "tag" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_1f22c73374bcca1ea84a4dca59" ON "tag" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_58876ee26a90170551027459bf" ON "tag" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_1c84215eb01fa457d0beeaee7f" ON "tag" ("tagTypeId") `); + await queryRunner.query(`DROP INDEX "IDX_c1bef0b8aa28d47b9907667a1f"`); + await queryRunner.query(`DROP INDEX "IDX_e2d517d3e3ff2a873f43367813"`); + await queryRunner.query(`DROP INDEX "IDX_556112dc97b482454726198f25"`); + await queryRunner.query(`DROP INDEX "IDX_dcefa726d2f515583535cb540f"`); + await queryRunner.query(`CREATE TABLE "temporary_tag_type" ("deletedAt" datetime, "id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "type" varchar NOT NULL, CONSTRAINT "FK_556112dc97b482454726198f250" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_dcefa726d2f515583535cb540f3" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE)`); + await queryRunner.query(`INSERT INTO "temporary_tag_type"("deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "type") SELECT "deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "type" FROM "tag_type"`); + await queryRunner.query(`DROP TABLE "tag_type"`); + await queryRunner.query(`ALTER TABLE "temporary_tag_type" RENAME TO "tag_type"`); + await queryRunner.query(`CREATE INDEX "IDX_c1bef0b8aa28d47b9907667a1f" ON "tag_type" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_e2d517d3e3ff2a873f43367813" ON "tag_type" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_556112dc97b482454726198f25" ON "tag_type" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_dcefa726d2f515583535cb540f" ON "tag_type" ("organizationId") `); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_dcefa726d2f515583535cb540f"`); + await queryRunner.query(`DROP INDEX "IDX_556112dc97b482454726198f25"`); + await queryRunner.query(`DROP INDEX "IDX_e2d517d3e3ff2a873f43367813"`); + await queryRunner.query(`DROP INDEX "IDX_c1bef0b8aa28d47b9907667a1f"`); + await queryRunner.query(`ALTER TABLE "tag_type" RENAME TO "temporary_tag_type"`); + await queryRunner.query(`CREATE TABLE "tag_type" ("deletedAt" datetime, "id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "archivedAt" datetime, "tenantId" varchar, "organizationId" varchar, "type" varchar NOT NULL)`); + await queryRunner.query(`INSERT INTO "tag_type"("deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "type") SELECT "deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "type" FROM "temporary_tag_type"`); + await queryRunner.query(`DROP TABLE "temporary_tag_type"`); + await queryRunner.query(`CREATE INDEX "IDX_dcefa726d2f515583535cb540f" ON "tag_type" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_556112dc97b482454726198f25" ON "tag_type" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_e2d517d3e3ff2a873f43367813" ON "tag_type" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_c1bef0b8aa28d47b9907667a1f" ON "tag_type" ("isActive") `); + await queryRunner.query(`DROP INDEX "IDX_1c84215eb01fa457d0beeaee7f"`); + await queryRunner.query(`DROP INDEX "IDX_58876ee26a90170551027459bf"`); + await queryRunner.query(`DROP INDEX "IDX_1f22c73374bcca1ea84a4dca59"`); + await queryRunner.query(`DROP INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9"`); + await queryRunner.query(`DROP INDEX "IDX_b08dd29fb6a8acdf83c83d8988"`); + await queryRunner.query(`DROP INDEX "IDX_49746602acc4e5e8721062b69e"`); + await queryRunner.query(`ALTER TABLE "tag" RENAME TO "temporary_tag"`); + await queryRunner.query(`CREATE TABLE "tag" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "description" varchar, "color" varchar NOT NULL, "isSystem" boolean NOT NULL DEFAULT (0), "icon" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "textColor" varchar, "fix_relational_custom_fields" boolean, "archivedAt" datetime, "tagTypeId" varchar, CONSTRAINT "FK_49746602acc4e5e8721062b69ec" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_b08dd29fb6a8acdf83c83d8988f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_c2f6bec0b39eaa3a6d90903ae99" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE)`); + await queryRunner.query(`INSERT INTO "tag"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt", "tagTypeId") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt", "tagTypeId" FROM "temporary_tag"`); + await queryRunner.query(`DROP TABLE "temporary_tag"`); + await queryRunner.query(`CREATE INDEX "IDX_1c84215eb01fa457d0beeaee7f" ON "tag" ("tagTypeId") `); + await queryRunner.query(`CREATE INDEX "IDX_58876ee26a90170551027459bf" ON "tag" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_1f22c73374bcca1ea84a4dca59" ON "tag" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9" ON "tag" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_b08dd29fb6a8acdf83c83d8988" ON "tag" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_49746602acc4e5e8721062b69e" ON "tag" ("organizationTeamId") `); + await queryRunner.query(`DROP INDEX "IDX_1c84215eb01fa457d0beeaee7f"`); + await queryRunner.query(`DROP INDEX "IDX_58876ee26a90170551027459bf"`); + await queryRunner.query(`DROP INDEX "IDX_1f22c73374bcca1ea84a4dca59"`); + await queryRunner.query(`DROP INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9"`); + await queryRunner.query(`DROP INDEX "IDX_b08dd29fb6a8acdf83c83d8988"`); + await queryRunner.query(`DROP INDEX "IDX_49746602acc4e5e8721062b69e"`); + await queryRunner.query(`ALTER TABLE "tag" RENAME TO "temporary_tag"`); + await queryRunner.query(`CREATE TABLE "tag" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "description" varchar, "color" varchar NOT NULL, "isSystem" boolean NOT NULL DEFAULT (0), "icon" varchar, "organizationTeamId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "deletedAt" datetime, "textColor" varchar, "fix_relational_custom_fields" boolean, "archivedAt" datetime, CONSTRAINT "FK_49746602acc4e5e8721062b69ec" FOREIGN KEY ("organizationTeamId") REFERENCES "organization_team" ("id") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_b08dd29fb6a8acdf83c83d8988f" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_c2f6bec0b39eaa3a6d90903ae99" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE)`); + await queryRunner.query(`INSERT INTO "tag"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "description", "color", "isSystem", "icon", "organizationTeamId", "isActive", "isArchived", "deletedAt", "textColor", "fix_relational_custom_fields", "archivedAt" FROM "temporary_tag"`); + await queryRunner.query(`DROP TABLE "temporary_tag"`); + await queryRunner.query(`CREATE INDEX "IDX_58876ee26a90170551027459bf" ON "tag" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_1f22c73374bcca1ea84a4dca59" ON "tag" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_c2f6bec0b39eaa3a6d90903ae9" ON "tag" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_b08dd29fb6a8acdf83c83d8988" ON "tag" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_49746602acc4e5e8721062b69e" ON "tag" ("organizationTeamId") `); + await queryRunner.query(`DROP INDEX "IDX_dcefa726d2f515583535cb540f"`); + await queryRunner.query(`DROP INDEX "IDX_556112dc97b482454726198f25"`); + await queryRunner.query(`DROP INDEX "IDX_e2d517d3e3ff2a873f43367813"`); + await queryRunner.query(`DROP INDEX "IDX_c1bef0b8aa28d47b9907667a1f"`); + await queryRunner.query(`DROP TABLE "tag_type"`); + } + + /** + * MySQL Up Migration + * + * @param queryRunner + */ + public async mysqlUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE \`tag_type\` (\`deletedAt\` datetime(6) NULL, \`id\` varchar(36) NOT NULL, \`createdAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6), \`updatedAt\` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), \`isActive\` tinyint NULL DEFAULT 1, \`isArchived\` tinyint NULL DEFAULT 0, \`archivedAt\` datetime NULL, \`tenantId\` varchar(255) NULL, \`organizationId\` varchar(255) NULL, \`type\` varchar(255) NOT NULL, INDEX \`IDX_c1bef0b8aa28d47b9907667a1f\` (\`isActive\`), INDEX \`IDX_e2d517d3e3ff2a873f43367813\` (\`isArchived\`), INDEX \`IDX_556112dc97b482454726198f25\` (\`tenantId\`), INDEX \`IDX_dcefa726d2f515583535cb540f\` (\`organizationId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB`); + await queryRunner.query(`ALTER TABLE \`tag\` ADD \`tagTypeId\` varchar(255) NULL`); + await queryRunner.query(`CREATE INDEX \`IDX_1c84215eb01fa457d0beeaee7f\` ON \`tag\` (\`tagTypeId\`)`); + await queryRunner.query(`ALTER TABLE \`tag\` ADD CONSTRAINT \`FK_1c84215eb01fa457d0beeaee7fc\` FOREIGN KEY (\`tagTypeId\`) REFERENCES \`tag_type\`(\`id\`) ON DELETE SET NULL ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`tag_type\` ADD CONSTRAINT \`FK_556112dc97b482454726198f250\` FOREIGN KEY (\`tenantId\`) REFERENCES \`tenant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE \`tag_type\` ADD CONSTRAINT \`FK_dcefa726d2f515583535cb540f3\` FOREIGN KEY (\`organizationId\`) REFERENCES \`organization\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE`); + } + + /** + * MySQL Down Migration + * + * @param queryRunner + */ + public async mysqlDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`tag_type\` DROP FOREIGN KEY \`FK_dcefa726d2f515583535cb540f3\``); + await queryRunner.query(`ALTER TABLE \`tag_type\` DROP FOREIGN KEY \`FK_556112dc97b482454726198f250\``); + await queryRunner.query(`ALTER TABLE \`tag\` DROP FOREIGN KEY \`FK_1c84215eb01fa457d0beeaee7fc\``); + await queryRunner.query(`DROP INDEX \`IDX_1c84215eb01fa457d0beeaee7f\` ON \`tag\``); + await queryRunner.query(`ALTER TABLE \`tag\` DROP COLUMN \`tagTypeId\``); + await queryRunner.query(`DROP INDEX \`IDX_dcefa726d2f515583535cb540f\` ON \`tag_type\``); + await queryRunner.query(`DROP INDEX \`IDX_556112dc97b482454726198f25\` ON \`tag_type\``); + await queryRunner.query(`DROP INDEX \`IDX_e2d517d3e3ff2a873f43367813\` ON \`tag_type\``); + await queryRunner.query(`DROP INDEX \`IDX_c1bef0b8aa28d47b9907667a1f\` ON \`tag_type\``); + await queryRunner.query(`DROP TABLE \`tag_type\``); + } +} diff --git a/packages/core/src/lib/tags/tag.entity.ts b/packages/core/src/lib/tags/tag.entity.ts index 52e2fa65fc1..541f89e967d 100644 --- a/packages/core/src/lib/tags/tag.entity.ts +++ b/packages/core/src/lib/tags/tag.entity.ts @@ -115,14 +115,23 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @MultiORMColumn({ default: false }) isSystem?: boolean; + /** Additional virtual columns */ @VirtualMultiOrmColumn() fullIconUrl?: string; + /* + |-------------------------------------------------------------------------- + | @ManyToOne + |-------------------------------------------------------------------------- + */ /** * TagType */ - @ApiProperty({ type: () => TagType }) @MultiORMManyToOne(() => TagType, (it) => it.tags, { + /** Indicates if the relation column value can be nullable or not. */ + nullable: true, + + /** Database cascade action on delete. */ onDelete: 'SET NULL' }) @JoinColumn() @@ -130,17 +139,11 @@ export class Tag extends TenantOrganizationBaseEntity implements ITag { @ApiProperty({ type: () => String }) @RelationId((it: Tag) => it.tagType) - @IsString() + @IsUUID() @ColumnIndex() @MultiORMColumn({ nullable: true, relationId: true }) tagTypeId?: ID; - /* - |-------------------------------------------------------------------------- - | @ManyToOne - |-------------------------------------------------------------------------- - */ - /** * Organization Team */