diff --git a/src/core/modules/data/service/base-data.service.ts b/src/core/modules/data/service/base-data.service.ts index 0d0c696..e60c841 100644 --- a/src/core/modules/data/service/base-data.service.ts +++ b/src/core/modules/data/service/base-data.service.ts @@ -21,6 +21,15 @@ export abstract class BaseDataService { return await queryRunner.manager.save(newEntity); } + async createMany( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + entity: Entity[], + ): Promise { + const newEntity = queryRunner.manager.create(entityTarget, entity); + return await queryRunner.manager.save(newEntity); + } + async createBatch( queryRunner: QueryRunner, entityTarget: EntityTarget, diff --git a/src/core/modules/domain/usecase/managers/base-change-position.manager.ts b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts new file mode 100644 index 0000000..5849753 --- /dev/null +++ b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts @@ -0,0 +1,152 @@ +import { BaseManager } from '../base.manager'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; +import { SelectQueryBuilder } from 'typeorm'; + +export abstract class BaseChangePosition extends BaseManager { + protected result: Entity; + protected duplicateColumn: string[]; + protected startData: Entity; + protected endData: Entity; + protected columnSort: string; + + protected firstDataId: number; + protected lastSort: number; + protected sortTo: number; + + abstract get entityTarget(): any; + + setData(entity: Entity, columnSort: string): void { + this.data = entity; + this.columnSort = columnSort; + } + + async beforeProcess(): Promise { + if (!this.data?.end || this.data.start == this.data?.end) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: 'Please drag to another position', + error: 'Unprocessable Entity', + }); + } + + this.startData = await this.dataService.getOneByOptions({ + where: { + id: this.data.start, + }, + }); + + if (!this.startData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Entity with id : ${this.data.start} not found`, + error: 'Unprocessable Entity', + }); + } + + this.endData = await this.dataService.getOneByOptions({ + where: { + id: this.data.end, + }, + }); + + if (!this.endData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Entity with id : ${this.data.end} not found`, + error: 'Unprocessable Entity', + }); + } + + if (this.endData[this.columnSort] > this.startData[this.columnSort]) { + // drag from up + this.firstDataId = this.startData[this.columnSort]; + this.lastSort = this.endData[this.columnSort]; + this.sortTo = this.lastSort; + } else if ( + this.endData[this.columnSort] < this.startData[this.columnSort] + ) { + // drag from bottom + this.firstDataId = this.endData[this.columnSort]; + this.lastSort = this.startData[this.columnSort]; + this.sortTo = this.firstDataId; + } + } + + async prepareData(): Promise { + Object.assign(this.data, { + creator_id: this.user.id, + creator_name: this.user.name, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + } + + async validateProcess(): Promise { + return; + } + + async process(): Promise { + let dataArrange: Entity[]; + + const queryBuilder = this.dataService + .getRepository() + .createQueryBuilder(this.tableName) + .where(`${this.tableName}.${this.columnSort} between :data1 and :data2`, { + data1: this.firstDataId, + data2: this.lastSort, + }); + + const datas = await queryBuilder + .orderBy(`${this.tableName}.${this.columnSort}`, 'ASC') + .getManyAndCount(); + + if (datas[0].length) { + let dataFirst = datas[0][0][this.columnSort]; + const data = datas[0]; + const length = datas[1]; + + if (this.endData[this.columnSort] > this.startData[this.columnSort]) { + // drag from above + const dataDragged = data[0]; + const arraySlice = data.slice(1, length); + dataArrange = arraySlice.concat([dataDragged]); + } else if ( + this.endData[this.columnSort] < this.startData[this.columnSort] + ) { + // drag from bottom + const dataDragged = data[length - 1]; + const arraySlice = data.slice(0, length - 1); + + dataArrange = [dataDragged].concat(arraySlice); + } + + for (let i = 0; i < length; i++) { + dataArrange[i][this.columnSort] = dataFirst; + dataFirst++; + } + + await this.dataService.createMany( + this.queryRunner, + this.entityTarget, + dataArrange, + ); + } + } + + get validateRelations(): validateRelations[] { + return []; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): string { + return `Success! Data ${this.startData['name']} successfully moved to ${this.sortTo}`; + } +} diff --git a/src/core/modules/infrastructure/dto/base-change-position.dto.ts b/src/core/modules/infrastructure/dto/base-change-position.dto.ts new file mode 100644 index 0000000..0b8a90c --- /dev/null +++ b/src/core/modules/infrastructure/dto/base-change-position.dto.ts @@ -0,0 +1,4 @@ +export class ChangePositionDto { + start: string; + end: string; +} diff --git a/src/database/migrations/1721284172572-update-sort-column.ts b/src/database/migrations/1721284172572-update-sort-column.ts new file mode 100644 index 0000000..cd3464d --- /dev/null +++ b/src/database/migrations/1721284172572-update-sort-column.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateSortColumn1721284172572 implements MigrationInterface { + name = 'UpdateSortColumn1721284172572'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "term_conditions" ADD "sort_order" integer NOT NULL DEFAULT '0'`, + ); + await queryRunner.query( + `ALTER TABLE "faqs" ADD "sort_order" integer NOT NULL DEFAULT '0'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "faqs" DROP COLUMN "sort_order"`); + await queryRunner.query( + `ALTER TABLE "term_conditions" DROP COLUMN "sort_order"`, + ); + } +} diff --git a/src/database/migrations/1721284234428-update-midtrans-column-transaction.ts b/src/database/migrations/1721284234428-update-midtrans-column-transaction.ts new file mode 100644 index 0000000..681c011 --- /dev/null +++ b/src/database/migrations/1721284234428-update-midtrans-column-transaction.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateMidtransColumnTransaction1721284234428 + implements MigrationInterface +{ + name = 'UpdateMidtransColumnTransaction1721284234428'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_url"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_token"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_midtrans_token" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_midtrans_url" character varying`, + ); + } +} diff --git a/src/modules/web-information/faq/data/models/faq.model.ts b/src/modules/web-information/faq/data/models/faq.model.ts index 3317f56..2027efd 100644 --- a/src/modules/web-information/faq/data/models/faq.model.ts +++ b/src/modules/web-information/faq/data/models/faq.model.ts @@ -5,6 +5,9 @@ import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; @Entity(TABLE_NAME.FAQ) export class FaqModel extends BaseStatusModel implements FaqEntity { + @Column('int', { name: 'sort_order', default: 0 }) + sort_order: number; + @Column('varchar', { name: 'title', nullable: true }) title: string; diff --git a/src/modules/web-information/faq/data/services/faq-data.service.ts b/src/modules/web-information/faq/data/services/faq-data.service.ts index ea40735..1327784 100644 --- a/src/modules/web-information/faq/data/services/faq-data.service.ts +++ b/src/modules/web-information/faq/data/services/faq-data.service.ts @@ -14,4 +14,16 @@ export class FaqDataService extends BaseDataService { ) { super(repo); } + + async getSortColumn(): Promise { + const query = this.repo.createQueryBuilder('data'); + + const sortColumn = await query + .select('data.sort_order') + .orderBy('data.sort_order', 'DESC') + .getOne(); + + const lastColumn = sortColumn?.sort_order ?? 0; + return lastColumn + 1; + } } diff --git a/src/modules/web-information/faq/domain/entities/faq.entity.ts b/src/modules/web-information/faq/domain/entities/faq.entity.ts index a25e3c6..80bff41 100644 --- a/src/modules/web-information/faq/domain/entities/faq.entity.ts +++ b/src/modules/web-information/faq/domain/entities/faq.entity.ts @@ -1,6 +1,7 @@ import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; export interface FaqEntity extends BaseStatusEntity { + sort_order: number; title: string; description: string; } diff --git a/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts b/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts index c82096c..46e2d6d 100644 --- a/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts +++ b/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts @@ -15,6 +15,7 @@ import { BatchInactiveFaqManager } from './managers/batch-inactive-faq.manager'; import { BatchActiveFaqManager } from './managers/batch-active-faq.manager'; import { BatchDeleteFaqManager } from './managers/batch-delete-faq.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { ChangePositionFaqManager } from './managers/change-position-faq.manager'; @Injectable() export class FaqDataOrchestrator extends BaseDataTransactionOrchestrator { @@ -29,11 +30,19 @@ export class FaqDataOrchestrator extends BaseDataTransactionOrchestrator { + this.changePositionManager.setData(data, 'sort_order'); + this.changePositionManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.changePositionManager.execute(); + return this.changePositionManager.getResult(); + } + async create(data): Promise { this.createManager.setData(data); this.createManager.setService(this.serviceData, TABLE_NAME.FAQ); diff --git a/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts new file mode 100644 index 0000000..ed717cd --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { BaseChangePosition } from 'src/core/modules/domain/usecase/managers/base-change-position.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { FaqModel } from '../../../data/models/faq.model'; + +@Injectable() +export class ChangePositionFaqManager extends BaseChangePosition { + get entityTarget(): any { + return FaqModel; + } + + async afterProcess(): Promise { + return; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts index dc79622..bc36684 100644 --- a/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts +++ b/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts @@ -12,6 +12,11 @@ import { FaqCreatedEvent } from '../../entities/event/faq-created.event'; @Injectable() export class CreateFaqManager extends BaseCreateManager { async beforeProcess(): Promise { + const sortColumn = await this.dataService.getSortColumn(); + + Object.assign(this.data, { + sort_order: sortColumn, + }); return; } diff --git a/src/modules/web-information/faq/faq.module.ts b/src/modules/web-information/faq/faq.module.ts index 0554fbe..dee08b1 100644 --- a/src/modules/web-information/faq/faq.module.ts +++ b/src/modules/web-information/faq/faq.module.ts @@ -22,6 +22,7 @@ import { BatchActiveFaqManager } from './domain/usecases/managers/batch-active-f import { BatchConfirmFaqManager } from './domain/usecases/managers/batch-confirm-faq.manager'; import { BatchInactiveFaqManager } from './domain/usecases/managers/batch-inactive-faq.manager'; import { FaqModel } from './data/models/faq.model'; +import { ChangePositionFaqManager } from './domain/usecases/managers/change-position-faq.manager'; @Module({ imports: [ @@ -43,6 +44,7 @@ import { FaqModel } from './data/models/faq.model'; BatchActiveFaqManager, BatchConfirmFaqManager, BatchInactiveFaqManager, + ChangePositionFaqManager, FaqDataService, FaqReadService, diff --git a/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts b/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts index 11e8346..96cffc1 100644 --- a/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts +++ b/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts @@ -2,8 +2,12 @@ import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.d import { FaqEntity } from '../../domain/entities/faq.entity'; import { IsString } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; export class FaqDto extends BaseStatusDto implements FaqEntity { + @Exclude() + sort_order: number; + @ApiProperty({ type: String, required: true, diff --git a/src/modules/web-information/faq/infrastructure/faq-data.controller.ts b/src/modules/web-information/faq/infrastructure/faq-data.controller.ts index 0a55d27..9fe59fa 100644 --- a/src/modules/web-information/faq/infrastructure/faq-data.controller.ts +++ b/src/modules/web-information/faq/infrastructure/faq-data.controller.ts @@ -15,6 +15,7 @@ import { FaqEntity } from '../domain/entities/faq.entity'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; import { Public } from 'src/core/guards'; +import { ChangePositionDto } from 'src/core/modules/infrastructure/dto/base-change-position.dto'; @ApiTags(`${MODULE_NAME.FAQ.split('-').join(' ')} - data`) @Controller(`v1/${MODULE_NAME.FAQ}`) @@ -28,6 +29,11 @@ export class FaqDataController { return await this.orchestrator.create(data); } + @Post('/change-position') + async dragDrop(@Body() body: ChangePositionDto): Promise { + return await this.orchestrator.changePostion(body); + } + @Put('/batch-delete') async batchDeleted(@Body() body: BatchIdsDto): Promise { return await this.orchestrator.batchDelete(body.ids); diff --git a/src/modules/web-information/term-condition/data/models/term-condition.model.ts b/src/modules/web-information/term-condition/data/models/term-condition.model.ts index 08a3ade..529570b 100644 --- a/src/modules/web-information/term-condition/data/models/term-condition.model.ts +++ b/src/modules/web-information/term-condition/data/models/term-condition.model.ts @@ -8,6 +8,9 @@ export class TermConditionModel extends BaseStatusModel implements TermConditionEntity { + @Column('int', { name: 'sort_order', default: 0 }) + sort_order: number; + @Column('varchar', { name: 'title', nullable: true }) title: string; diff --git a/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts b/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts index 832e472..607abb8 100644 --- a/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts +++ b/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts @@ -14,4 +14,16 @@ export class TermConditionDataService extends BaseDataService { + const query = this.repo.createQueryBuilder('data'); + + const sortColumn = await query + .select('data.sort_order') + .orderBy('data.sort_order', 'DESC') + .getOne(); + + const lastColumn = sortColumn?.sort_order ?? 0; + return lastColumn + 1; + } } diff --git a/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts b/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts index 3e662e3..c477277 100644 --- a/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts +++ b/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts @@ -1,6 +1,7 @@ import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; export interface TermConditionEntity extends BaseStatusEntity { + sort_order: number; title: string; description: string; } diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts new file mode 100644 index 0000000..ff7d1a3 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { BaseChangePosition } from 'src/core/modules/domain/usecase/managers/base-change-position.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; + +@Injectable() +export class ChangePositionTermConditionManager extends BaseChangePosition { + get entityTarget(): any { + return TermConditionModel; + } + + async afterProcess(): Promise { + return; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts index ab08d99..2a0d2ef 100644 --- a/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts +++ b/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts @@ -12,6 +12,11 @@ import { TermConditionCreatedEvent } from '../../entities/event/term-condition-c @Injectable() export class CreateTermConditionManager extends BaseCreateManager { async beforeProcess(): Promise { + const sortColumn = await this.dataService.getSortColumn(); + + Object.assign(this.data, { + sort_order: sortColumn, + }); return; } diff --git a/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts b/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts index c1b41f9..e7d8dd3 100644 --- a/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts +++ b/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts @@ -15,6 +15,7 @@ import { BatchInactiveTermConditionManager } from './managers/batch-inactive-ter import { BatchActiveTermConditionManager } from './managers/batch-active-term-condition.manager'; import { BatchDeleteTermConditionManager } from './managers/batch-delete-term-condition.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { ChangePositionTermConditionManager } from './managers/change-position-term-condition.manager'; @Injectable() export class TermConditionDataOrchestrator extends BaseDataTransactionOrchestrator { @@ -29,11 +30,22 @@ export class TermConditionDataOrchestrator extends BaseDataTransactionOrchestrat private batchActiveManager: BatchActiveTermConditionManager, private batchConfirmManager: BatchConfirmTermConditionManager, private batchInactiveManager: BatchInactiveTermConditionManager, + private changePositionManager: ChangePositionTermConditionManager, private serviceData: TermConditionDataService, ) { super(); } + async changePostion(data): Promise { + this.changePositionManager.setData(data, 'sort_order'); + this.changePositionManager.setService( + this.serviceData, + TABLE_NAME.TERM_CONDITION, + ); + await this.changePositionManager.execute(); + return this.changePositionManager.getResult(); + } + async create(data): Promise { this.createManager.setData(data); this.createManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); diff --git a/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts b/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts index b0eb01f..063a244 100644 --- a/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts +++ b/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts @@ -2,11 +2,15 @@ import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.d import { TermConditionEntity } from '../../domain/entities/term-condition.entity'; import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator'; +import { Exclude } from 'class-transformer'; export class TermConditionDto extends BaseStatusDto implements TermConditionEntity { + @Exclude() + sort_order: number; + @ApiProperty({ type: String, required: true, diff --git a/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts b/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts index 2f497c9..592b4ad 100644 --- a/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts +++ b/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts @@ -15,6 +15,7 @@ import { TermConditionEntity } from '../domain/entities/term-condition.entity'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; import { Public } from 'src/core/guards'; +import { ChangePositionDto } from 'src/core/modules/infrastructure/dto/base-change-position.dto'; @ApiTags(`${MODULE_NAME.TERM_CONDITION.split('-').join(' ')} - data`) @Controller(`v1/${MODULE_NAME.TERM_CONDITION}`) @@ -28,6 +29,11 @@ export class TermConditionDataController { return await this.orchestrator.create(data); } + @Post('/change-position') + async dragDrop(@Body() body: ChangePositionDto): Promise { + return await this.orchestrator.changePostion(body); + } + @Put('/batch-delete') async batchDeleted(@Body() body: BatchIdsDto): Promise { return await this.orchestrator.batchDelete(body.ids); diff --git a/src/modules/web-information/term-condition/term-condition.module.ts b/src/modules/web-information/term-condition/term-condition.module.ts index 0f2e156..7c110ed 100644 --- a/src/modules/web-information/term-condition/term-condition.module.ts +++ b/src/modules/web-information/term-condition/term-condition.module.ts @@ -22,6 +22,7 @@ import { BatchActiveTermConditionManager } from './domain/usecases/managers/batc import { BatchConfirmTermConditionManager } from './domain/usecases/managers/batch-confirm-term-condition.manager'; import { BatchInactiveTermConditionManager } from './domain/usecases/managers/batch-inactive-term-condition.manager'; import { TermConditionModel } from './data/models/term-condition.model'; +import { ChangePositionTermConditionManager } from './domain/usecases/managers/change-position-term-condition.manager'; @Module({ imports: [ @@ -43,6 +44,7 @@ import { TermConditionModel } from './data/models/term-condition.model'; BatchActiveTermConditionManager, BatchConfirmTermConditionManager, BatchInactiveTermConditionManager, + ChangePositionTermConditionManager, TermConditionDataService, TermConditionReadService,