From ab4d76bd9e5ad1504f9b198ca226e9c35e6ab655 Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Sun, 13 Oct 2024 06:55:38 +0200 Subject: [PATCH 1/2] feat: add resource link model and entity --- packages/contracts/src/activity-log.model.ts | 27 +------ packages/contracts/src/base-entity.model.ts | 20 +++++ packages/contracts/src/index.ts | 4 +- packages/contracts/src/resource-link.model.ts | 19 +++++ .../src/activity-log/activity-log.entity.ts | 16 +--- .../src/activity-log/activity-log.helper.ts | 8 +- .../activity-log/dto/get-activity-logs.dto.ts | 10 +-- .../organization-project-module.service.ts | 10 +-- .../organization-project.service.ts | 6 +- .../organization-sprint.service.ts | 10 +-- .../src/resource-link/repository/index.ts | 2 + .../mikro-orm-resource-link.repository.ts | 4 + .../type-orm-resource-link.repository.ts | 11 +++ .../src/resource-link/resource-link.entity.ts | 79 +++++++++++++++++++ .../commands/handlers/task-create.handler.ts | 12 +-- packages/core/src/tasks/task.service.ts | 6 +- 16 files changed, 171 insertions(+), 73 deletions(-) create mode 100644 packages/contracts/src/resource-link.model.ts create mode 100644 packages/core/src/resource-link/repository/index.ts create mode 100644 packages/core/src/resource-link/repository/mikro-orm-resource-link.repository.ts create mode 100644 packages/core/src/resource-link/repository/type-orm-resource-link.repository.ts create mode 100644 packages/core/src/resource-link/resource-link.entity.ts diff --git a/packages/contracts/src/activity-log.model.ts b/packages/contracts/src/activity-log.model.ts index a4e8d7c5aa0..96a6ae41633 100644 --- a/packages/contracts/src/activity-log.model.ts +++ b/packages/contracts/src/activity-log.model.ts @@ -1,11 +1,11 @@ -import { ActorTypeEnum, IBasePerTenantAndOrganizationEntityModel, ID, JsonData } from './base-entity.model'; +import { ActorTypeEnum, EntityEnum, IBasePerTenantAndOrganizationEntityModel, ID, JsonData } from './base-entity.model'; import { IUser } from './user.model'; /** * Interface representing an activity log entry. */ export interface IActivityLog extends IBasePerTenantAndOrganizationEntityModel { - entity: ActivityLogEntityEnum; // Entity / Table name concerned by activity log + entity: EntityEnum; // Entity / Table name concerned by activity log entityId: ID; // The ID of the element we are interacting with (a task, an organization, an employee, ...) action: ActionTypeEnum; actorType?: ActorTypeEnum; @@ -33,29 +33,6 @@ export enum ActionTypeEnum { Deleted = 'Deleted' } -/** - * Enum for entities that can be involved in activity logs. - */ -export enum ActivityLogEntityEnum { - Candidate = 'Candidate', - Contact = 'Contact', - Employee = 'Employee', - Expense = 'Expense', - DailyPlan = 'DailyPlan', - Invoice = 'Invoice', - Income = 'Income', - Organization = 'Organization', - OrganizationContact = 'OrganizationContact', - OrganizationDepartment = 'OrganizationDepartment', - OrganizationDocument = 'OrganizationDocument', - OrganizationProject = 'OrganizationProject', - OrganizationTeam = 'OrganizationTeam', - OrganizationProjectModule = 'OrganizationProjectModule', - OrganizationSprint = 'OrganizationSprint', - Task = 'Task', - User = 'User' -} - /** * Input type for activity log creation, excluding `creatorId` and `creator`. */ diff --git a/packages/contracts/src/base-entity.model.ts b/packages/contracts/src/base-entity.model.ts index ec84da5a8fa..a8e17a638c6 100644 --- a/packages/contracts/src/base-entity.model.ts +++ b/packages/contracts/src/base-entity.model.ts @@ -64,3 +64,23 @@ export enum ActorTypeEnum { System = 0, // System performed the action User = 1 // User performed the action } + +export enum EntityEnum { + Candidate = 'Candidate', + Contact = 'Contact', + Employee = 'Employee', + Expense = 'Expense', + DailyPlan = 'DailyPlan', + Invoice = 'Invoice', + Income = 'Income', + Organization = 'Organization', + OrganizationContact = 'OrganizationContact', + OrganizationDepartment = 'OrganizationDepartment', + OrganizationDocument = 'OrganizationDocument', + OrganizationProject = 'OrganizationProject', + OrganizationTeam = 'OrganizationTeam', + OrganizationProjectModule = 'OrganizationProjectModule', + OrganizationSprint = 'OrganizationSprint', + Task = 'Task', + User = 'User' +} diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index 59910f44137..b689e79eeac 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -105,6 +105,7 @@ export * from './report.model'; export * from './request-approval-employee.model'; export * from './request-approval-team.model'; export * from './request-approval.model'; +export * from './resource-link.model'; export * from './role-permission.model'; export * from './role.model'; export * from './screenshot.model'; @@ -144,7 +145,8 @@ export { IBaseSoftDeleteEntityModel, IBaseRelationsEntityModel, ActorTypeEnum, - JsonData + JsonData, + EntityEnum } from './base-entity.model'; export * from './proxy.model'; diff --git a/packages/contracts/src/resource-link.model.ts b/packages/contracts/src/resource-link.model.ts new file mode 100644 index 00000000000..39e1b8b1c20 --- /dev/null +++ b/packages/contracts/src/resource-link.model.ts @@ -0,0 +1,19 @@ +import { IURLMetaData } from './timesheet.model'; +import { EntityEnum, IBasePerTenantAndOrganizationEntityModel, ID } from './base-entity.model'; +import { IUser } from './user.model'; + +export interface IResourceLink extends IBasePerTenantAndOrganizationEntityModel { + entity: EntityEnum; + entityId: ID; + title: string; + url: string; + creator?: IUser; + creatorId?: ID; + metaData?: string | IURLMetaData; +} + +export interface IResourceLinkCreateInput extends Omit {} + +export interface IResourceLinkUpdateInput extends Partial> {} + +export interface IResourceLinkFindInput extends Pick {} diff --git a/packages/core/src/activity-log/activity-log.entity.ts b/packages/core/src/activity-log/activity-log.entity.ts index 4b1c9eaa902..a265db2bb53 100644 --- a/packages/core/src/activity-log/activity-log.entity.ts +++ b/packages/core/src/activity-log/activity-log.entity.ts @@ -3,15 +3,7 @@ import { EntityRepositoryType } from '@mikro-orm/core'; import { JoinColumn, RelationId } from 'typeorm'; import { IsArray, IsEnum, IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator'; import { isMySQL, isPostgres } from '@gauzy/config'; -import { - ActivityLogEntityEnum, - ActionTypeEnum, - ActorTypeEnum, - IActivityLog, - ID, - IUser, - JsonData -} from '@gauzy/contracts'; +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 { MikroOrmActivityLogRepository } from './repository/mikro-orm-activity-log.repository'; @@ -20,12 +12,12 @@ import { MikroOrmActivityLogRepository } from './repository/mikro-orm-activity-l export class ActivityLog extends TenantOrganizationBaseEntity implements IActivityLog { [EntityRepositoryType]?: MikroOrmActivityLogRepository; - @ApiProperty({ type: () => String, enum: ActivityLogEntityEnum }) + @ApiProperty({ enum: EntityEnum }) @IsNotEmpty() - @IsEnum(ActivityLogEntityEnum) + @IsEnum(EntityEnum) @ColumnIndex() @MultiORMColumn() - entity: ActivityLogEntityEnum; + entity: EntityEnum; @ApiProperty({ type: () => String }) @IsNotEmpty() diff --git a/packages/core/src/activity-log/activity-log.helper.ts b/packages/core/src/activity-log/activity-log.helper.ts index 260e53724b0..62e01691644 100644 --- a/packages/core/src/activity-log/activity-log.helper.ts +++ b/packages/core/src/activity-log/activity-log.helper.ts @@ -1,4 +1,4 @@ -import { ActionTypeEnum, ActivityLogEntityEnum, IActivityLogUpdatedValues } from '@gauzy/contracts'; +import { ActionTypeEnum, EntityEnum, IActivityLogUpdatedValues } from '@gauzy/contracts'; const ActivityTemplates = { [ActionTypeEnum.Created]: `{action} a new {entity} called "{entityName}"`, @@ -13,11 +13,7 @@ const ActivityTemplates = { * @param entityName - The name of the specific entity instance. * @returns A formatted description string. */ -export function generateActivityLogDescription( - action: ActionTypeEnum, - entity: ActivityLogEntityEnum, - entityName: string -): string { +export function generateActivityLogDescription(action: ActionTypeEnum, entity: EntityEnum, entityName: string): string { // Get the template corresponding to the action const template = ActivityTemplates[action] || '{action} {entity} "{entityName}"'; diff --git a/packages/core/src/activity-log/dto/get-activity-logs.dto.ts b/packages/core/src/activity-log/dto/get-activity-logs.dto.ts index c416cd21c93..b0b0d40d614 100644 --- a/packages/core/src/activity-log/dto/get-activity-logs.dto.ts +++ b/packages/core/src/activity-log/dto/get-activity-logs.dto.ts @@ -1,6 +1,6 @@ import { ApiPropertyOptional, IntersectionType, PickType } from '@nestjs/swagger'; import { IsEnum, IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; -import { ActionTypeEnum, ActivityLogEntityEnum, ActorTypeEnum, ID } from '@gauzy/contracts'; +import { ActionTypeEnum, EntityEnum, ActorTypeEnum, ID } from '@gauzy/contracts'; import { PaginationParams } from '../../core/crud'; import { TenantOrganizationBaseDTO } from '../../core/dto'; import { ActivityLog } from '../activity-log.entity'; @@ -18,10 +18,10 @@ export class GetActivityLogsDTO extends IntersectionType( PickType(ActivityLog, ['isActive', 'isArchived']) ) { // Filter by entity (example: Organization, Task, OrganizationContact) - @ApiPropertyOptional({ type: () => String, enum: ActivityLogEntityEnum }) + @ApiPropertyOptional({ enum: EntityEnum }) @IsOptional() - @IsEnum(ActivityLogEntityEnum) - entity: ActivityLogEntityEnum; + @IsEnum(EntityEnum) + entity: EntityEnum; // Filter by entityId (example: projectId, taskId, organizationContactId) @ApiPropertyOptional({ type: () => String }) @@ -30,7 +30,7 @@ export class GetActivityLogsDTO extends IntersectionType( entityId: ID; // Filter by action (example: CREATED, UPDATED, DELETED) - @ApiPropertyOptional({ type: () => String, enum: ActionTypeEnum }) + @ApiPropertyOptional({ enum: ActionTypeEnum }) @IsOptional() @IsEnum(ActionTypeEnum) action: ActionTypeEnum; diff --git a/packages/core/src/organization-project-module/organization-project-module.service.ts b/packages/core/src/organization-project-module/organization-project-module.service.ts index 57f059acee9..f0205c7b318 100644 --- a/packages/core/src/organization-project-module/organization-project-module.service.ts +++ b/packages/core/src/organization-project-module/organization-project-module.service.ts @@ -3,7 +3,7 @@ import { EventBus } from '@nestjs/cqrs'; import { Brackets, FindManyOptions, SelectQueryBuilder, UpdateResult, WhereExpressionBuilder } from 'typeorm'; import { ActionTypeEnum, - ActivityLogEntityEnum, + EntityEnum, ActorTypeEnum, ID, IOrganizationProjectModule, @@ -54,14 +54,14 @@ export class OrganizationProjectModuleService extends TenantAwareCrudService {} diff --git a/packages/core/src/resource-link/repository/type-orm-resource-link.repository.ts b/packages/core/src/resource-link/repository/type-orm-resource-link.repository.ts new file mode 100644 index 00000000000..d7673989321 --- /dev/null +++ b/packages/core/src/resource-link/repository/type-orm-resource-link.repository.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Repository } from 'typeorm'; +import { ResourceLink } from '../resource-link.entity'; + +@Injectable() +export class TypeOrmResourceLinkRepository extends Repository { + constructor(@InjectRepository(ResourceLink) readonly repository: Repository) { + super(repository.target, repository.manager, repository.queryRunner); + } +} diff --git a/packages/core/src/resource-link/resource-link.entity.ts b/packages/core/src/resource-link/resource-link.entity.ts new file mode 100644 index 00000000000..9849183f046 --- /dev/null +++ b/packages/core/src/resource-link/resource-link.entity.ts @@ -0,0 +1,79 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { EntityRepositoryType } from '@mikro-orm/core'; +import { JoinColumn, RelationId } from 'typeorm'; +import { IsEnum, IsNotEmpty, IsOptional, IsString, IsUrl, IsUUID } from 'class-validator'; +import { EntityEnum, ID, IResourceLink, IURLMetaData, IUser } from '@gauzy/contracts'; +import { isBetterSqlite3, isSqlite } from '@gauzy/config'; +import { TenantOrganizationBaseEntity, User } from '../core/entities/internal'; +import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity'; +import { MikroOrmResourceLinkRepository } from './repository/mikro-orm-resource-link.repository'; + +@MultiORMEntity('resource_links', { mikroOrmRepository: () => MikroOrmResourceLinkRepository }) +export class ResourceLink extends TenantOrganizationBaseEntity implements IResourceLink { + [EntityRepositoryType]?: MikroOrmResourceLinkRepository; + + @ApiProperty({ type: () => String, enum: EntityEnum }) + @IsNotEmpty() + @IsEnum(EntityEnum) + @ColumnIndex() + @MultiORMColumn() + entity: EntityEnum; + + @ApiProperty({ type: () => String }) + @IsNotEmpty() + @IsUUID() + @ColumnIndex() + @MultiORMColumn() + entityId: ID; + + @ApiProperty({ type: () => String }) + @IsNotEmpty() + @IsString() + @MultiORMColumn() + title: string; + + @ApiProperty({ type: () => String }) + @IsNotEmpty() + @IsUrl() + @MultiORMColumn({ type: 'text' }) + url: string; + + @ApiPropertyOptional({ + type: () => (isSqlite() || isBetterSqlite3() ? String : Object) + }) + @IsOptional() + @MultiORMColumn({ + nullable: true, + type: isSqlite() || isBetterSqlite3() ? 'text' : 'json' + }) + metaData?: string | IURLMetaData; + + /* + |-------------------------------------------------------------------------- + | @ManyToOne + |-------------------------------------------------------------------------- + */ + + /** + * User comment author + */ + @ApiPropertyOptional({ type: () => Object }) + @IsOptional() + @MultiORMManyToOne(() => User, { + /** Indicates if relation column value can be nullable or not. */ + nullable: true, + + /** Database cascade action on delete. */ + onDelete: 'CASCADE' + }) + @JoinColumn() + creator?: IUser; + + @ApiPropertyOptional({ type: () => String }) + @IsOptional() + @IsUUID() + @RelationId((it: ResourceLink) => it.creator) + @ColumnIndex() + @MultiORMColumn({ nullable: true, relationId: true }) + creatorId?: string; +} diff --git a/packages/core/src/tasks/commands/handlers/task-create.handler.ts b/packages/core/src/tasks/commands/handlers/task-create.handler.ts index 20255083409..6d66f508730 100644 --- a/packages/core/src/tasks/commands/handlers/task-create.handler.ts +++ b/packages/core/src/tasks/commands/handlers/task-create.handler.ts @@ -1,6 +1,6 @@ import { CommandHandler, ICommandHandler, EventBus as CqrsEventBus } from '@nestjs/cqrs'; import { HttpException, HttpStatus, Logger } from '@nestjs/common'; -import { ActionTypeEnum, ActivityLogEntityEnum, ActorTypeEnum, ITask } from '@gauzy/contracts'; +import { ActionTypeEnum, EntityEnum, ActorTypeEnum, ITask } from '@gauzy/contracts'; import { ActivityLogEvent } from '../../../activity-log/events'; import { generateActivityLogDescription } from '../../../activity-log/activity-log.helper'; import { EventBus } from '../../../event-bus'; @@ -75,18 +75,14 @@ export class TaskCreateHandler implements ICommandHandler { } // Generate the activity log description - const description = generateActivityLogDescription( - ActionTypeEnum.Created, - ActivityLogEntityEnum.Task, - task.title - ); + const description = generateActivityLogDescription(ActionTypeEnum.Created, EntityEnum.Task, task.title); console.log(`Generating activity log description: ${description}`); // Emit an event to log the activity this._cqrsEventBus.publish( new ActivityLogEvent({ - entity: ActivityLogEntityEnum.Task, + entity: EntityEnum.Task, entityId: task.id, action: ActionTypeEnum.Created, actorType: ActorTypeEnum.User, // TODO : Since we have Github Integration, make sure we can also store "System" for actor @@ -99,7 +95,7 @@ export class TaskCreateHandler implements ICommandHandler { console.log( `Task created with ID: ${task.id} with activity log: ${JSON.stringify({ - entity: ActivityLogEntityEnum.Task, + entity: EntityEnum.Task, entityId: task.id, action: ActionTypeEnum.Created, actorType: ActorTypeEnum.User, // TODO : Since we have Github Integration, make sure we can also store "System" for actor diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 144af042baa..c1e27c5cd54 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -14,7 +14,7 @@ import { import { isBoolean, isUUID } from 'class-validator'; import { ActionTypeEnum, - ActivityLogEntityEnum, + EntityEnum, ActorTypeEnum, ID, IEmployee, @@ -104,7 +104,7 @@ export class TaskService extends TenantAwareCrudService { // Generate the activity log description const description = generateActivityLogDescription( ActionTypeEnum.Updated, - ActivityLogEntityEnum.Task, + EntityEnum.Task, updatedTask.title ); @@ -116,7 +116,7 @@ export class TaskService extends TenantAwareCrudService { // Emit an event to log the activity this._eventBus.publish( new ActivityLogEvent({ - entity: ActivityLogEntityEnum.Task, + entity: EntityEnum.Task, entityId: updatedTask.id, action: ActionTypeEnum.Updated, actorType: ActorTypeEnum.User, // TODO : Since we have Github Integration, make sure we can also store "System" for actor From fea108aed47640ee2c651d9bc46bd4615612d74e Mon Sep 17 00:00:00 2001 From: GloireMutaliko21 Date: Sun, 13 Oct 2024 09:12:15 +0200 Subject: [PATCH 2/2] feat: resource link migration --- packages/core/src/core/entities/index.ts | 2 + packages/core/src/core/entities/internal.ts | 1 + .../1728798743598-CreateResourceLinkTable.ts | 216 ++++++++++++++++++ .../src/resource-link/resource-link.entity.ts | 2 +- 4 files changed, 220 insertions(+), 1 deletion(-) create mode 100644 packages/core/src/database/migrations/1728798743598-CreateResourceLinkTable.ts diff --git a/packages/core/src/core/entities/index.ts b/packages/core/src/core/entities/index.ts index 6be3ef456f2..4ddfdad5c6b 100644 --- a/packages/core/src/core/entities/index.ts +++ b/packages/core/src/core/entities/index.ts @@ -117,6 +117,7 @@ import { RequestApproval, RequestApprovalEmployee, RequestApprovalTeam, + ResourceLink, Role, RolePermission, Screenshot, @@ -266,6 +267,7 @@ export const coreEntities = [ RequestApproval, RequestApprovalEmployee, RequestApprovalTeam, + ResourceLink, Role, RolePermission, Screenshot, diff --git a/packages/core/src/core/entities/internal.ts b/packages/core/src/core/entities/internal.ts index e25ead320a7..f620c4b1079 100644 --- a/packages/core/src/core/entities/internal.ts +++ b/packages/core/src/core/entities/internal.ts @@ -121,6 +121,7 @@ export * from '../../reports/report.entity'; export * from '../../request-approval-employee/request-approval-employee.entity'; export * from '../../request-approval-team/request-approval-team.entity'; export * from '../../request-approval/request-approval.entity'; +export * from '../../resource-link/resource-link.entity'; export * from '../../role-permission/role-permission.entity'; export * from '../../role/role.entity'; export * from '../../skills/skill.entity'; diff --git a/packages/core/src/database/migrations/1728798743598-CreateResourceLinkTable.ts b/packages/core/src/database/migrations/1728798743598-CreateResourceLinkTable.ts new file mode 100644 index 00000000000..bded25a05c1 --- /dev/null +++ b/packages/core/src/database/migrations/1728798743598-CreateResourceLinkTable.ts @@ -0,0 +1,216 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; +import { yellow } from 'chalk'; +import { DatabaseTypeEnum } from '@gauzy/config'; + +export class CreateResourceLinkTable1728798743598 implements MigrationInterface { + name = 'CreateResourceLinkTable1728798743598'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + console.log(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 "resource_link" ("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, "entity" character varying NOT NULL, "entityId" character varying NOT NULL, "title" character varying NOT NULL, "url" text NOT NULL, "metaData" json, "creatorId" uuid, CONSTRAINT "PK_c4bd5617a9b97fff39d02de2456" PRIMARY KEY ("id"))` + ); + await queryRunner.query(`CREATE INDEX "IDX_841b729b80bc03ea38d16b8508" ON "resource_link" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff" ON "resource_link" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_f9438f82f6e93bd6a87b8216af" ON "resource_link" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_95603855ae10050123e48a6688" ON "resource_link" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_44100d3eaf418ee67fa7a756f1" ON "resource_link" ("entity") `); + await queryRunner.query(`CREATE INDEX "IDX_b73c278619bd8fb7f30f93182c" ON "resource_link" ("entityId") `); + await queryRunner.query(`CREATE INDEX "IDX_2ef674d18792e8864fd8d484ea" ON "resource_link" ("creatorId") `); + await queryRunner.query( + `ALTER TABLE "resource_link" ADD CONSTRAINT "FK_f9438f82f6e93bd6a87b8216af9" FOREIGN KEY ("tenantId") REFERENCES "tenant"("id") ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE "resource_link" ADD CONSTRAINT "FK_95603855ae10050123e48a66881" FOREIGN KEY ("organizationId") REFERENCES "organization"("id") ON DELETE CASCADE ON UPDATE CASCADE` + ); + await queryRunner.query( + `ALTER TABLE "resource_link" ADD CONSTRAINT "FK_2ef674d18792e8864fd8d484eac" FOREIGN KEY ("creatorId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "resource_link" DROP CONSTRAINT "FK_2ef674d18792e8864fd8d484eac"`); + await queryRunner.query(`ALTER TABLE "resource_link" DROP CONSTRAINT "FK_95603855ae10050123e48a66881"`); + await queryRunner.query(`ALTER TABLE "resource_link" DROP CONSTRAINT "FK_f9438f82f6e93bd6a87b8216af9"`); + await queryRunner.query(`DROP INDEX "public"."IDX_2ef674d18792e8864fd8d484ea"`); + await queryRunner.query(`DROP INDEX "public"."IDX_b73c278619bd8fb7f30f93182c"`); + await queryRunner.query(`DROP INDEX "public"."IDX_44100d3eaf418ee67fa7a756f1"`); + await queryRunner.query(`DROP INDEX "public"."IDX_95603855ae10050123e48a6688"`); + await queryRunner.query(`DROP INDEX "public"."IDX_f9438f82f6e93bd6a87b8216af"`); + await queryRunner.query(`DROP INDEX "public"."IDX_4c25c2c9d7ebbd0c07edd824ff"`); + await queryRunner.query(`DROP INDEX "public"."IDX_841b729b80bc03ea38d16b8508"`); + await queryRunner.query(`DROP TABLE "resource_link"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "resource_link" ("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, "entity" varchar NOT NULL, "entityId" varchar NOT NULL, "title" varchar NOT NULL, "url" text NOT NULL, "metaData" text, "creatorId" varchar)` + ); + await queryRunner.query(`CREATE INDEX "IDX_841b729b80bc03ea38d16b8508" ON "resource_link" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff" ON "resource_link" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_f9438f82f6e93bd6a87b8216af" ON "resource_link" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_95603855ae10050123e48a6688" ON "resource_link" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_44100d3eaf418ee67fa7a756f1" ON "resource_link" ("entity") `); + await queryRunner.query(`CREATE INDEX "IDX_b73c278619bd8fb7f30f93182c" ON "resource_link" ("entityId") `); + await queryRunner.query(`CREATE INDEX "IDX_2ef674d18792e8864fd8d484ea" ON "resource_link" ("creatorId") `); + await queryRunner.query(`DROP INDEX "IDX_841b729b80bc03ea38d16b8508"`); + await queryRunner.query(`DROP INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff"`); + await queryRunner.query(`DROP INDEX "IDX_f9438f82f6e93bd6a87b8216af"`); + await queryRunner.query(`DROP INDEX "IDX_95603855ae10050123e48a6688"`); + await queryRunner.query(`DROP INDEX "IDX_44100d3eaf418ee67fa7a756f1"`); + await queryRunner.query(`DROP INDEX "IDX_b73c278619bd8fb7f30f93182c"`); + await queryRunner.query(`DROP INDEX "IDX_2ef674d18792e8864fd8d484ea"`); + await queryRunner.query( + `CREATE TABLE "temporary_resource_link" ("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, "entity" varchar NOT NULL, "entityId" varchar NOT NULL, "title" varchar NOT NULL, "url" text NOT NULL, "metaData" text, "creatorId" varchar, CONSTRAINT "FK_f9438f82f6e93bd6a87b8216af9" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_95603855ae10050123e48a66881" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_2ef674d18792e8864fd8d484eac" FOREIGN KEY ("creatorId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)` + ); + await queryRunner.query( + `INSERT INTO "temporary_resource_link"("deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "entity", "entityId", "title", "url", "metaData", "creatorId") SELECT "deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "entity", "entityId", "title", "url", "metaData", "creatorId" FROM "resource_link"` + ); + await queryRunner.query(`DROP TABLE "resource_link"`); + await queryRunner.query(`ALTER TABLE "temporary_resource_link" RENAME TO "resource_link"`); + await queryRunner.query(`CREATE INDEX "IDX_841b729b80bc03ea38d16b8508" ON "resource_link" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff" ON "resource_link" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_f9438f82f6e93bd6a87b8216af" ON "resource_link" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_95603855ae10050123e48a6688" ON "resource_link" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_44100d3eaf418ee67fa7a756f1" ON "resource_link" ("entity") `); + await queryRunner.query(`CREATE INDEX "IDX_b73c278619bd8fb7f30f93182c" ON "resource_link" ("entityId") `); + await queryRunner.query(`CREATE INDEX "IDX_2ef674d18792e8864fd8d484ea" ON "resource_link" ("creatorId") `); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_2ef674d18792e8864fd8d484ea"`); + await queryRunner.query(`DROP INDEX "IDX_b73c278619bd8fb7f30f93182c"`); + await queryRunner.query(`DROP INDEX "IDX_44100d3eaf418ee67fa7a756f1"`); + await queryRunner.query(`DROP INDEX "IDX_95603855ae10050123e48a6688"`); + await queryRunner.query(`DROP INDEX "IDX_f9438f82f6e93bd6a87b8216af"`); + await queryRunner.query(`DROP INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff"`); + await queryRunner.query(`DROP INDEX "IDX_841b729b80bc03ea38d16b8508"`); + await queryRunner.query(`ALTER TABLE "resource_link" RENAME TO "temporary_resource_link"`); + await queryRunner.query( + `CREATE TABLE "resource_link" ("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, "entity" varchar NOT NULL, "entityId" varchar NOT NULL, "title" varchar NOT NULL, "url" text NOT NULL, "metaData" text, "creatorId" varchar)` + ); + await queryRunner.query( + `INSERT INTO "resource_link"("deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "entity", "entityId", "title", "url", "metaData", "creatorId") SELECT "deletedAt", "id", "createdAt", "updatedAt", "isActive", "isArchived", "archivedAt", "tenantId", "organizationId", "entity", "entityId", "title", "url", "metaData", "creatorId" FROM "temporary_resource_link"` + ); + await queryRunner.query(`DROP TABLE "temporary_resource_link"`); + await queryRunner.query(`CREATE INDEX "IDX_2ef674d18792e8864fd8d484ea" ON "resource_link" ("creatorId") `); + await queryRunner.query(`CREATE INDEX "IDX_b73c278619bd8fb7f30f93182c" ON "resource_link" ("entityId") `); + await queryRunner.query(`CREATE INDEX "IDX_44100d3eaf418ee67fa7a756f1" ON "resource_link" ("entity") `); + await queryRunner.query(`CREATE INDEX "IDX_95603855ae10050123e48a6688" ON "resource_link" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_f9438f82f6e93bd6a87b8216af" ON "resource_link" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff" ON "resource_link" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_841b729b80bc03ea38d16b8508" ON "resource_link" ("isActive") `); + await queryRunner.query(`DROP INDEX "IDX_2ef674d18792e8864fd8d484ea"`); + await queryRunner.query(`DROP INDEX "IDX_b73c278619bd8fb7f30f93182c"`); + await queryRunner.query(`DROP INDEX "IDX_44100d3eaf418ee67fa7a756f1"`); + await queryRunner.query(`DROP INDEX "IDX_95603855ae10050123e48a6688"`); + await queryRunner.query(`DROP INDEX "IDX_f9438f82f6e93bd6a87b8216af"`); + await queryRunner.query(`DROP INDEX "IDX_4c25c2c9d7ebbd0c07edd824ff"`); + await queryRunner.query(`DROP INDEX "IDX_841b729b80bc03ea38d16b8508"`); + await queryRunner.query(`DROP TABLE "resource_link"`); + } + + /** + * MySQL Up Migration + * + * @param queryRunner + */ + public async mysqlUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE \`resource_link\` (\`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, \`entity\` varchar(255) NOT NULL, \`entityId\` varchar(255) NOT NULL, \`title\` varchar(255) NOT NULL, \`url\` text NOT NULL, \`metaData\` json NULL, \`creatorId\` varchar(255) NULL, INDEX \`IDX_841b729b80bc03ea38d16b8508\` (\`isActive\`), INDEX \`IDX_4c25c2c9d7ebbd0c07edd824ff\` (\`isArchived\`), INDEX \`IDX_f9438f82f6e93bd6a87b8216af\` (\`tenantId\`), INDEX \`IDX_95603855ae10050123e48a6688\` (\`organizationId\`), INDEX \`IDX_44100d3eaf418ee67fa7a756f1\` (\`entity\`), INDEX \`IDX_b73c278619bd8fb7f30f93182c\` (\`entityId\`), INDEX \`IDX_2ef674d18792e8864fd8d484ea\` (\`creatorId\`), PRIMARY KEY (\`id\`)) ENGINE=InnoDB` + ); + await queryRunner.query( + `ALTER TABLE \`resource_link\` ADD CONSTRAINT \`FK_f9438f82f6e93bd6a87b8216af9\` FOREIGN KEY (\`tenantId\`) REFERENCES \`tenant\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + await queryRunner.query( + `ALTER TABLE \`resource_link\` ADD CONSTRAINT \`FK_95603855ae10050123e48a66881\` FOREIGN KEY (\`organizationId\`) REFERENCES \`organization\`(\`id\`) ON DELETE CASCADE ON UPDATE CASCADE` + ); + await queryRunner.query( + `ALTER TABLE \`resource_link\` ADD CONSTRAINT \`FK_2ef674d18792e8864fd8d484eac\` FOREIGN KEY (\`creatorId\`) REFERENCES \`user\`(\`id\`) ON DELETE CASCADE ON UPDATE NO ACTION` + ); + } + + /** + * MySQL Down Migration + * + * @param queryRunner + */ + public async mysqlDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`resource_link\` DROP FOREIGN KEY \`FK_2ef674d18792e8864fd8d484eac\``); + await queryRunner.query(`ALTER TABLE \`resource_link\` DROP FOREIGN KEY \`FK_95603855ae10050123e48a66881\``); + await queryRunner.query(`ALTER TABLE \`resource_link\` DROP FOREIGN KEY \`FK_f9438f82f6e93bd6a87b8216af9\``); + await queryRunner.query(`DROP INDEX \`IDX_2ef674d18792e8864fd8d484ea\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_b73c278619bd8fb7f30f93182c\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_44100d3eaf418ee67fa7a756f1\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_95603855ae10050123e48a6688\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_f9438f82f6e93bd6a87b8216af\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_4c25c2c9d7ebbd0c07edd824ff\` ON \`resource_link\``); + await queryRunner.query(`DROP INDEX \`IDX_841b729b80bc03ea38d16b8508\` ON \`resource_link\``); + await queryRunner.query(`DROP TABLE \`resource_link\``); + } +} diff --git a/packages/core/src/resource-link/resource-link.entity.ts b/packages/core/src/resource-link/resource-link.entity.ts index 9849183f046..bb84ae49767 100644 --- a/packages/core/src/resource-link/resource-link.entity.ts +++ b/packages/core/src/resource-link/resource-link.entity.ts @@ -8,7 +8,7 @@ import { TenantOrganizationBaseEntity, User } from '../core/entities/internal'; import { ColumnIndex, MultiORMColumn, MultiORMEntity, MultiORMManyToOne } from '../core/decorators/entity'; import { MikroOrmResourceLinkRepository } from './repository/mikro-orm-resource-link.repository'; -@MultiORMEntity('resource_links', { mikroOrmRepository: () => MikroOrmResourceLinkRepository }) +@MultiORMEntity('resource_link', { mikroOrmRepository: () => MikroOrmResourceLinkRepository }) export class ResourceLink extends TenantOrganizationBaseEntity implements IResourceLink { [EntityRepositoryType]?: MikroOrmResourceLinkRepository;