From 382bdd4ef1870b1efc92321922d42e5e8e09932a Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 07:44:06 +0100 Subject: [PATCH 01/32] feat: implement dynamic create DTO for Conversation --- api/src/chat/dto/conversation.dto.ts | 9 +++++-- .../repositories/conversation.repository.ts | 4 +++- api/src/chat/schemas/conversation.schema.ts | 12 +++++----- api/src/chat/services/conversation.service.ts | 4 +++- api/src/utils/generics/base-repository.ts | 4 +++- api/src/utils/generics/base-service.ts | 13 ++++++---- api/src/utils/test/fixtures/conversation.ts | 21 ++++++++-------- api/src/utils/types/dto.types.ts | 24 +++++++++++++++++++ 8 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 api/src/utils/types/dto.types.ts diff --git a/api/src/chat/dto/conversation.dto.ts b/api/src/chat/dto/conversation.dto.ts index 81d978f9..e519fc00 100644 --- a/api/src/chat/dto/conversation.dto.ts +++ b/api/src/chat/dto/conversation.dto.ts @@ -16,6 +16,7 @@ import { IsString, } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; import { Context } from './../schemas/types/context'; @@ -45,7 +46,7 @@ export class ConversationCreateDto { @IsObjectId({ message: 'Current must be a valid objectId', }) - current: string; + current?: string; @ApiProperty({ description: 'next conversation', type: Array }) @IsOptional() @@ -54,5 +55,9 @@ export class ConversationCreateDto { each: true, message: 'next must be a valid objectId', }) - next: string[]; + next?: string[]; } + +export type IConversationDto = DtoConfig<{ + create: ConversationCreateDto; +}>; diff --git a/api/src/chat/repositories/conversation.repository.ts b/api/src/chat/repositories/conversation.repository.ts index 0cde3eeb..28e4461f 100644 --- a/api/src/chat/repositories/conversation.repository.ts +++ b/api/src/chat/repositories/conversation.repository.ts @@ -13,6 +13,7 @@ import { Model } from 'mongoose'; import { BaseRepository } from '@/utils/generics/base-repository'; +import { IConversationDto } from '../dto/conversation.dto'; import { Conversation, CONVERSATION_POPULATE, @@ -24,7 +25,8 @@ import { export class ConversationRepository extends BaseRepository< Conversation, ConversationPopulate, - ConversationFull + ConversationFull, + IConversationDto > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/schemas/conversation.schema.ts b/api/src/chat/schemas/conversation.schema.ts index 7880c0c6..efa21a60 100644 --- a/api/src/chat/schemas/conversation.schema.ts +++ b/api/src/chat/schemas/conversation.schema.ts @@ -51,19 +51,19 @@ class ConversationStub extends BaseSchema { type: Boolean, default: true, }) - active?: boolean; + active: boolean; @Prop({ type: Object, default: getDefaultConversationContext(), }) - context?: Context; + context: Context; @Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Block', }) - current?: unknown; + current: unknown; @Prop([ { @@ -72,7 +72,7 @@ class ConversationStub extends BaseSchema { default: [], }, ]) - next?: unknown; + next: unknown; } @Schema({ timestamps: true }) @@ -81,10 +81,10 @@ export class Conversation extends ConversationStub { sender: string; @Transform(({ obj }) => obj.current.toString()) - current?: string; + current: string; @Transform(({ obj }) => obj.next.map((elem) => elem.toString())) - next?: string[]; + next: string[]; } @Schema({ timestamps: true }) diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index fdd65cd2..bb8f0464 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -12,6 +12,7 @@ import EventWrapper from '@/channel/lib/EventWrapper'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; +import { IConversationDto } from '../dto/conversation.dto'; import { VIEW_MORE_PAYLOAD } from '../helpers/constants'; import { ConversationRepository } from '../repositories/conversation.repository'; import { Block, BlockFull } from '../schemas/block.schema'; @@ -30,7 +31,8 @@ import { SubscriberService } from './subscriber.service'; export class ConversationService extends BaseService< Conversation, ConversationPopulate, - ConversationFull + ConversationFull, + IConversationDto > { constructor( readonly repository: ConversationRepository, diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 65167f1f..f16eac79 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -30,6 +30,7 @@ import { import { TFilterQuery } from '@/utils/types/filter.types'; import { PageQueryDto, QuerySortDto } from '../pagination/pagination-query.dto'; +import { DtoInfer, DtoOperations, DtoProps } from '../types/dto.types'; import { BaseSchema } from './base-schema'; import { LifecycleHookManager } from './lifecycle-hook-manager'; @@ -70,6 +71,7 @@ export abstract class BaseRepository< T extends FlattenMaps, P extends string = never, TFull extends Omit = never, + DTO extends DtoProps = unknown, U = Omit, D = Document, > { @@ -454,7 +456,7 @@ export abstract class BaseRepository< return await this.model.countDocuments(criteria).exec(); } - async create(dto: U): Promise { + async create(dto: DtoInfer): Promise { const doc = await this.model.create(dto); return plainToClass( diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index 70ec686b..e2a3485a 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -14,6 +14,7 @@ import { ProjectionType, QueryOptions } from 'mongoose'; import { TFilterQuery } from '@/utils/types/filter.types'; import { PageQueryDto, QuerySortDto } from '../pagination/pagination-query.dto'; +import { DtoInfer, DtoOperations, DtoProps } from '../types/dto.types'; import { BaseRepository } from './base-repository'; import { BaseSchema } from './base-schema'; @@ -22,6 +23,7 @@ export abstract class BaseService< T extends BaseSchema, P extends string = never, TFull extends Omit = never, + DTO extends DtoProps = unknown, > { constructor(protected readonly repository: BaseRepository) {} @@ -140,7 +142,9 @@ export abstract class BaseService< return await this.repository.count(criteria); } - async create>(dto: D): Promise { + async create< + D extends DtoInfer>, + >(dto: D): Promise { try { return await this.repository.create(dto); } catch (error) { @@ -153,10 +157,9 @@ export abstract class BaseService< } } - async findOneOrCreate>( - criteria: string | TFilterQuery, - dto: D, - ): Promise { + async findOneOrCreate< + D extends DtoInfer>, + >(criteria: string | TFilterQuery, dto: D): Promise { const result = await this.findOne(criteria); if (!result) { return await this.create(dto); diff --git a/api/src/utils/test/fixtures/conversation.ts b/api/src/utils/test/fixtures/conversation.ts index 548a84b2..fca24ce1 100644 --- a/api/src/utils/test/fixtures/conversation.ts +++ b/api/src/utils/test/fixtures/conversation.ts @@ -9,10 +9,7 @@ import mongoose from 'mongoose'; import { ConversationCreateDto } from '@/chat/dto/conversation.dto'; -import { - Conversation, - ConversationModel, -} from '@/chat/schemas/conversation.schema'; +import { ConversationModel } from '@/chat/schemas/conversation.schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; import { TFixturesDefaultValues } from '../types'; @@ -116,14 +113,16 @@ const conversations: ConversationCreateDto[] = [ }, ]; -export const conversationDefaultValues: TFixturesDefaultValues = { - active: false, -}; +export const conversationDefaultValues: TFixturesDefaultValues = + { + active: false, + }; -export const conversationFixtures = getFixturesWithDefaultValues({ - fixtures: conversations, - defaultValues: conversationDefaultValues, -}); +export const conversationFixtures = + getFixturesWithDefaultValues({ + fixtures: conversations, + defaultValues: conversationDefaultValues, + }); export const installConversationTypeFixtures = async () => { const subscribers = await installSubscriberFixtures(); diff --git a/api/src/utils/types/dto.types.ts b/api/src/utils/types/dto.types.ts new file mode 100644 index 00000000..6edd682f --- /dev/null +++ b/api/src/utils/types/dto.types.ts @@ -0,0 +1,24 @@ +/* + * Copyright © 2025 Hexastack. All rights reserved. + * + * Licensed under the GNU Affero General Public License v3.0 (AGPLv3) with the following additional terms: + * 1. The name "Hexabot" is a trademark of Hexastack. You may not use this name in derivative works without express written permission. + * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). + */ + +export enum DtoOperations { + Create = 'create', + Read = 'read', + Update = 'update', + Delete = 'delete', +} + +export type DtoConfig>> = T; + +export type DtoProps> = { + [K in DtoOperations]?: T[K]; +}; + +export type DtoInfer = DTO[K] extends object + ? DTO[K] + : T; From 7f59f593c5c5315ced5080b9b3cb1d8c3a2be657 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 07:57:37 +0100 Subject: [PATCH 02/32] feat: implement dynamic create DTO for User --- api/src/user/dto/user.dto.ts | 5 +++++ api/src/user/schemas/user.schema.ts | 20 ++++++++++---------- api/src/user/services/user.service.ts | 8 +++++++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index 2b082b6e..c4d3c437 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -22,6 +22,7 @@ import { IsString, } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; export class UserCreateDto { @@ -98,3 +99,7 @@ export class UserResetPasswordDto extends PickType(UserCreateDto, [ ]) {} export class UserRequestResetDto extends PickType(UserCreateDto, ['email']) {} + +export type TUserCrudsDto = DtoConfig<{ + create: UserCreateDto; +}>; diff --git a/api/src/user/schemas/user.schema.ts b/api/src/user/schemas/user.schema.ts index a97d2057..5d5755f0 100644 --- a/api/src/user/schemas/user.schema.ts +++ b/api/src/user/schemas/user.schema.ts @@ -50,6 +50,16 @@ export class UserStub extends BaseSchema { }) password: string; + @Prop([{ type: MongooseSchema.Types.ObjectId, ref: 'Role' }]) + roles: unknown; + + @Prop({ + type: MongooseSchema.Types.ObjectId, + ref: 'Attachment', + default: null, + }) + avatar: unknown; + @Prop({ type: Boolean, default: false, @@ -83,16 +93,6 @@ export class UserStub extends BaseSchema { }) resetCount?: number; - @Prop([{ type: MongooseSchema.Types.ObjectId, ref: 'Role' }]) - roles: unknown; - - @Prop({ - type: MongooseSchema.Types.ObjectId, - ref: 'Attachment', - default: null, - }) - avatar: unknown; - @Prop({ type: String, default: null, diff --git a/api/src/user/services/user.service.ts b/api/src/user/services/user.service.ts index 39c2e57a..a9387c19 100644 --- a/api/src/user/services/user.service.ts +++ b/api/src/user/services/user.service.ts @@ -10,11 +10,17 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; +import { TUserCrudsDto } from '../dto/user.dto'; import { UserRepository } from '../repositories/user.repository'; import { User, UserFull, UserPopulate } from '../schemas/user.schema'; @Injectable() -export class UserService extends BaseService { +export class UserService extends BaseService< + User, + UserPopulate, + UserFull, + TUserCrudsDto +> { constructor(readonly repository: UserRepository) { super(repository); } From 239909e71123c2d4d13ef850e0509ef434d1dd95 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 07:59:04 +0100 Subject: [PATCH 03/32] fix: update conversation DTO naming --- api/src/chat/dto/conversation.dto.ts | 2 +- api/src/chat/repositories/conversation.repository.ts | 4 ++-- api/src/chat/services/conversation.service.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/src/chat/dto/conversation.dto.ts b/api/src/chat/dto/conversation.dto.ts index e519fc00..f3c08e57 100644 --- a/api/src/chat/dto/conversation.dto.ts +++ b/api/src/chat/dto/conversation.dto.ts @@ -58,6 +58,6 @@ export class ConversationCreateDto { next?: string[]; } -export type IConversationDto = DtoConfig<{ +export type TConversationCrudsDto = DtoConfig<{ create: ConversationCreateDto; }>; diff --git a/api/src/chat/repositories/conversation.repository.ts b/api/src/chat/repositories/conversation.repository.ts index 28e4461f..a8cade65 100644 --- a/api/src/chat/repositories/conversation.repository.ts +++ b/api/src/chat/repositories/conversation.repository.ts @@ -13,7 +13,7 @@ import { Model } from 'mongoose'; import { BaseRepository } from '@/utils/generics/base-repository'; -import { IConversationDto } from '../dto/conversation.dto'; +import { TConversationCrudsDto } from '../dto/conversation.dto'; import { Conversation, CONVERSATION_POPULATE, @@ -26,7 +26,7 @@ export class ConversationRepository extends BaseRepository< Conversation, ConversationPopulate, ConversationFull, - IConversationDto + TConversationCrudsDto > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index bb8f0464..59f121ba 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -12,7 +12,7 @@ import EventWrapper from '@/channel/lib/EventWrapper'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; -import { IConversationDto } from '../dto/conversation.dto'; +import { TConversationCrudsDto } from '../dto/conversation.dto'; import { VIEW_MORE_PAYLOAD } from '../helpers/constants'; import { ConversationRepository } from '../repositories/conversation.repository'; import { Block, BlockFull } from '../schemas/block.schema'; @@ -32,7 +32,7 @@ export class ConversationService extends BaseService< Conversation, ConversationPopulate, ConversationFull, - IConversationDto + TConversationCrudsDto > { constructor( readonly repository: ConversationRepository, From 849f458f1c2a139577eff8754f019c25f3bc4f69 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 08:11:20 +0100 Subject: [PATCH 04/32] fix: pass dynamic DTO from the base service to the repository constructor --- api/src/utils/generics/base-service.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index e2a3485a..01fe33d3 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -25,7 +25,9 @@ export abstract class BaseService< TFull extends Omit = never, DTO extends DtoProps = unknown, > { - constructor(protected readonly repository: BaseRepository) {} + constructor( + protected readonly repository: BaseRepository, + ) {} getRepository() { return this.repository; From 13e94e5079f182982caf6b14a2ec684ff4d0b028 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 08:25:55 +0100 Subject: [PATCH 05/32] fix: update DTOs naming --- api/src/chat/dto/conversation.dto.ts | 2 +- api/src/chat/repositories/conversation.repository.ts | 4 ++-- api/src/chat/services/conversation.service.ts | 4 ++-- api/src/user/dto/user.dto.ts | 2 +- api/src/user/repositories/user.repository.ts | 5 +++-- api/src/user/services/user.service.ts | 4 ++-- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/api/src/chat/dto/conversation.dto.ts b/api/src/chat/dto/conversation.dto.ts index f3c08e57..9396f856 100644 --- a/api/src/chat/dto/conversation.dto.ts +++ b/api/src/chat/dto/conversation.dto.ts @@ -58,6 +58,6 @@ export class ConversationCreateDto { next?: string[]; } -export type TConversationCrudsDto = DtoConfig<{ +export type ConversationCrudsDto = DtoConfig<{ create: ConversationCreateDto; }>; diff --git a/api/src/chat/repositories/conversation.repository.ts b/api/src/chat/repositories/conversation.repository.ts index a8cade65..cc3c01a9 100644 --- a/api/src/chat/repositories/conversation.repository.ts +++ b/api/src/chat/repositories/conversation.repository.ts @@ -13,7 +13,7 @@ import { Model } from 'mongoose'; import { BaseRepository } from '@/utils/generics/base-repository'; -import { TConversationCrudsDto } from '../dto/conversation.dto'; +import { ConversationCrudsDto } from '../dto/conversation.dto'; import { Conversation, CONVERSATION_POPULATE, @@ -26,7 +26,7 @@ export class ConversationRepository extends BaseRepository< Conversation, ConversationPopulate, ConversationFull, - TConversationCrudsDto + ConversationCrudsDto > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index 59f121ba..430e3166 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -12,7 +12,7 @@ import EventWrapper from '@/channel/lib/EventWrapper'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; -import { TConversationCrudsDto } from '../dto/conversation.dto'; +import { ConversationCrudsDto } from '../dto/conversation.dto'; import { VIEW_MORE_PAYLOAD } from '../helpers/constants'; import { ConversationRepository } from '../repositories/conversation.repository'; import { Block, BlockFull } from '../schemas/block.schema'; @@ -32,7 +32,7 @@ export class ConversationService extends BaseService< Conversation, ConversationPopulate, ConversationFull, - TConversationCrudsDto + ConversationCrudsDto > { constructor( readonly repository: ConversationRepository, diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index c4d3c437..f8800f42 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -100,6 +100,6 @@ export class UserResetPasswordDto extends PickType(UserCreateDto, [ export class UserRequestResetDto extends PickType(UserCreateDto, ['email']) {} -export type TUserCrudsDto = DtoConfig<{ +export type UserCrudsDto = DtoConfig<{ create: UserCreateDto; }>; diff --git a/api/src/user/repositories/user.repository.ts b/api/src/user/repositories/user.repository.ts index e4a27f7b..2a87884f 100644 --- a/api/src/user/repositories/user.repository.ts +++ b/api/src/user/repositories/user.repository.ts @@ -20,7 +20,7 @@ import { import { BaseRepository } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { UserEditProfileDto } from '../dto/user.dto'; +import { UserCrudsDto, UserEditProfileDto } from '../dto/user.dto'; import { User, USER_POPULATE, @@ -34,7 +34,8 @@ import { hash } from '../utilities/bcryptjs'; export class UserRepository extends BaseRepository< User, UserPopulate, - UserFull + UserFull, + UserCrudsDto > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/user/services/user.service.ts b/api/src/user/services/user.service.ts index a9387c19..d1cca1c5 100644 --- a/api/src/user/services/user.service.ts +++ b/api/src/user/services/user.service.ts @@ -10,7 +10,7 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; -import { TUserCrudsDto } from '../dto/user.dto'; +import { UserCrudsDto } from '../dto/user.dto'; import { UserRepository } from '../repositories/user.repository'; import { User, UserFull, UserPopulate } from '../schemas/user.schema'; @@ -19,7 +19,7 @@ export class UserService extends BaseService< User, UserPopulate, UserFull, - TUserCrudsDto + UserCrudsDto > { constructor(readonly repository: UserRepository) { super(repository); From 5c4d9af9cf0efee2ef3e4bffdc0db82dc7c04648 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 08:32:40 +0100 Subject: [PATCH 06/32] fix: update the DTOs types naming --- api/src/chat/dto/conversation.dto.ts | 2 +- .../chat/repositories/conversation.repository.ts | 4 ++-- api/src/chat/services/conversation.service.ts | 4 ++-- api/src/user/dto/user.dto.ts | 2 +- api/src/user/repositories/user.repository.ts | 4 ++-- api/src/user/services/user.service.ts | 4 ++-- api/src/utils/generics/base-repository.ts | 4 ++-- api/src/utils/generics/base-service.ts | 16 ++++++++++++---- 8 files changed, 24 insertions(+), 16 deletions(-) diff --git a/api/src/chat/dto/conversation.dto.ts b/api/src/chat/dto/conversation.dto.ts index 9396f856..0bb2cb82 100644 --- a/api/src/chat/dto/conversation.dto.ts +++ b/api/src/chat/dto/conversation.dto.ts @@ -58,6 +58,6 @@ export class ConversationCreateDto { next?: string[]; } -export type ConversationCrudsDto = DtoConfig<{ +export type ConversationDTOCruds = DtoConfig<{ create: ConversationCreateDto; }>; diff --git a/api/src/chat/repositories/conversation.repository.ts b/api/src/chat/repositories/conversation.repository.ts index cc3c01a9..40429a65 100644 --- a/api/src/chat/repositories/conversation.repository.ts +++ b/api/src/chat/repositories/conversation.repository.ts @@ -13,7 +13,7 @@ import { Model } from 'mongoose'; import { BaseRepository } from '@/utils/generics/base-repository'; -import { ConversationCrudsDto } from '../dto/conversation.dto'; +import { ConversationDTOCruds } from '../dto/conversation.dto'; import { Conversation, CONVERSATION_POPULATE, @@ -26,7 +26,7 @@ export class ConversationRepository extends BaseRepository< Conversation, ConversationPopulate, ConversationFull, - ConversationCrudsDto + ConversationDTOCruds > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index 430e3166..4f791ea6 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -12,7 +12,7 @@ import EventWrapper from '@/channel/lib/EventWrapper'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; -import { ConversationCrudsDto } from '../dto/conversation.dto'; +import { ConversationDTOCruds } from '../dto/conversation.dto'; import { VIEW_MORE_PAYLOAD } from '../helpers/constants'; import { ConversationRepository } from '../repositories/conversation.repository'; import { Block, BlockFull } from '../schemas/block.schema'; @@ -32,7 +32,7 @@ export class ConversationService extends BaseService< Conversation, ConversationPopulate, ConversationFull, - ConversationCrudsDto + ConversationDTOCruds > { constructor( readonly repository: ConversationRepository, diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index f8800f42..bda90053 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -100,6 +100,6 @@ export class UserResetPasswordDto extends PickType(UserCreateDto, [ export class UserRequestResetDto extends PickType(UserCreateDto, ['email']) {} -export type UserCrudsDto = DtoConfig<{ +export type UserDTOCruds = DtoConfig<{ create: UserCreateDto; }>; diff --git a/api/src/user/repositories/user.repository.ts b/api/src/user/repositories/user.repository.ts index 2a87884f..4b372c17 100644 --- a/api/src/user/repositories/user.repository.ts +++ b/api/src/user/repositories/user.repository.ts @@ -20,7 +20,7 @@ import { import { BaseRepository } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { UserCrudsDto, UserEditProfileDto } from '../dto/user.dto'; +import { UserDTOCruds, UserEditProfileDto } from '../dto/user.dto'; import { User, USER_POPULATE, @@ -35,7 +35,7 @@ export class UserRepository extends BaseRepository< User, UserPopulate, UserFull, - UserCrudsDto + UserDTOCruds > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/user/services/user.service.ts b/api/src/user/services/user.service.ts index d1cca1c5..294a822f 100644 --- a/api/src/user/services/user.service.ts +++ b/api/src/user/services/user.service.ts @@ -10,7 +10,7 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; -import { UserCrudsDto } from '../dto/user.dto'; +import { UserDTOCruds } from '../dto/user.dto'; import { UserRepository } from '../repositories/user.repository'; import { User, UserFull, UserPopulate } from '../schemas/user.schema'; @@ -19,7 +19,7 @@ export class UserService extends BaseService< User, UserPopulate, UserFull, - UserCrudsDto + UserDTOCruds > { constructor(readonly repository: UserRepository) { super(repository); diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index f16eac79..9de85f8c 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -71,7 +71,7 @@ export abstract class BaseRepository< T extends FlattenMaps, P extends string = never, TFull extends Omit = never, - DTO extends DtoProps = unknown, + DTOCruds extends DtoProps = unknown, U = Omit, D = Document, > { @@ -456,7 +456,7 @@ export abstract class BaseRepository< return await this.model.countDocuments(criteria).exec(); } - async create(dto: DtoInfer): Promise { + async create(dto: DtoInfer): Promise { const doc = await this.model.create(dto); return plainToClass( diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index 01fe33d3..652fd0a4 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -23,10 +23,10 @@ export abstract class BaseService< T extends BaseSchema, P extends string = never, TFull extends Omit = never, - DTO extends DtoProps = unknown, + DTOCruds extends DtoProps = unknown, > { constructor( - protected readonly repository: BaseRepository, + protected readonly repository: BaseRepository, ) {} getRepository() { @@ -145,7 +145,11 @@ export abstract class BaseService< } async create< - D extends DtoInfer>, + D extends DtoInfer< + DtoOperations.Create, + DTOCruds, + Omit + >, >(dto: D): Promise { try { return await this.repository.create(dto); @@ -160,7 +164,11 @@ export abstract class BaseService< } async findOneOrCreate< - D extends DtoInfer>, + D extends DtoInfer< + DtoOperations.Create, + DTOCruds, + Omit + >, >(criteria: string | TFilterQuery, dto: D): Promise { const result = await this.findOne(criteria); if (!result) { From 7489648baea4df8f48c401019ace37b15f33d427 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 08:54:04 +0100 Subject: [PATCH 07/32] fix: support createMany method --- api/src/utils/generics/base-repository.ts | 4 +++- api/src/utils/generics/base-service.ts | 23 ++++++++--------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 9de85f8c..63a2b145 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -466,7 +466,9 @@ export abstract class BaseRepository< ); } - async createMany(dtoArray: U[]): Promise { + async createMany( + dtoArray: DtoInfer[], + ): Promise { const docs = await this.model.create(dtoArray); return docs.map((doc) => diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index 652fd0a4..0b1389c0 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -144,13 +144,9 @@ export abstract class BaseService< return await this.repository.count(criteria); } - async create< - D extends DtoInfer< - DtoOperations.Create, - DTOCruds, - Omit - >, - >(dto: D): Promise { + async create>( + dto: DtoInfer, + ): Promise { try { return await this.repository.create(dto); } catch (error) { @@ -163,13 +159,10 @@ export abstract class BaseService< } } - async findOneOrCreate< - D extends DtoInfer< - DtoOperations.Create, - DTOCruds, - Omit - >, - >(criteria: string | TFilterQuery, dto: D): Promise { + async findOneOrCreate>( + criteria: string | TFilterQuery, + dto: DtoInfer, + ): Promise { const result = await this.findOne(criteria); if (!result) { return await this.create(dto); @@ -178,7 +171,7 @@ export abstract class BaseService< } async createMany>( - dtoArray: D[], + dtoArray: DtoInfer[], ): Promise { return await this.repository.createMany(dtoArray); } From e5e5495a4299cc8e91b1d2a79fc81b202c5189cc Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 09:11:23 +0100 Subject: [PATCH 08/32] fix: update user.dto state property --- api/src/user/dto/user.dto.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index bda90053..ce706e7d 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -62,12 +62,21 @@ export class UserCreateDto { @IsString() @IsObjectId({ message: 'Avatar must be a valid ObjectId' }) avatar: string | null = null; + + @ApiPropertyOptional({ + description: 'User state', + type: Boolean, + }) + @IsOptional() + @IsBoolean() + state?: boolean; } export class UserEditProfileDto extends OmitType(PartialType(UserCreateDto), [ 'username', 'roles', 'avatar', + 'state', ]) { @ApiPropertyOptional({ description: 'User language', type: String }) @IsOptional() From 1b788cc78f481e205f6ff3210c28373fe97433f0 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 09:11:54 +0100 Subject: [PATCH 09/32] fix: enhance enum naming --- api/src/utils/generics/base-repository.ts | 6 +++--- api/src/utils/generics/base-service.ts | 8 ++++---- api/src/utils/types/dto.types.ts | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/src/utils/generics/base-repository.ts b/api/src/utils/generics/base-repository.ts index 63a2b145..ce96100f 100644 --- a/api/src/utils/generics/base-repository.ts +++ b/api/src/utils/generics/base-repository.ts @@ -30,7 +30,7 @@ import { import { TFilterQuery } from '@/utils/types/filter.types'; import { PageQueryDto, QuerySortDto } from '../pagination/pagination-query.dto'; -import { DtoInfer, DtoOperations, DtoProps } from '../types/dto.types'; +import { DtoActions, DtoInfer, DtoProps } from '../types/dto.types'; import { BaseSchema } from './base-schema'; import { LifecycleHookManager } from './lifecycle-hook-manager'; @@ -456,7 +456,7 @@ export abstract class BaseRepository< return await this.model.countDocuments(criteria).exec(); } - async create(dto: DtoInfer): Promise { + async create(dto: DtoInfer): Promise { const doc = await this.model.create(dto); return plainToClass( @@ -467,7 +467,7 @@ export abstract class BaseRepository< } async createMany( - dtoArray: DtoInfer[], + dtoArray: DtoInfer[], ): Promise { const docs = await this.model.create(dtoArray); diff --git a/api/src/utils/generics/base-service.ts b/api/src/utils/generics/base-service.ts index 0b1389c0..bcf3045c 100644 --- a/api/src/utils/generics/base-service.ts +++ b/api/src/utils/generics/base-service.ts @@ -14,7 +14,7 @@ import { ProjectionType, QueryOptions } from 'mongoose'; import { TFilterQuery } from '@/utils/types/filter.types'; import { PageQueryDto, QuerySortDto } from '../pagination/pagination-query.dto'; -import { DtoInfer, DtoOperations, DtoProps } from '../types/dto.types'; +import { DtoActions, DtoInfer, DtoProps } from '../types/dto.types'; import { BaseRepository } from './base-repository'; import { BaseSchema } from './base-schema'; @@ -145,7 +145,7 @@ export abstract class BaseService< } async create>( - dto: DtoInfer, + dto: DtoInfer, ): Promise { try { return await this.repository.create(dto); @@ -161,7 +161,7 @@ export abstract class BaseService< async findOneOrCreate>( criteria: string | TFilterQuery, - dto: DtoInfer, + dto: DtoInfer, ): Promise { const result = await this.findOne(criteria); if (!result) { @@ -171,7 +171,7 @@ export abstract class BaseService< } async createMany>( - dtoArray: DtoInfer[], + dtoArray: DtoInfer[], ): Promise { return await this.repository.createMany(dtoArray); } diff --git a/api/src/utils/types/dto.types.ts b/api/src/utils/types/dto.types.ts index 6edd682f..34bfe0d5 100644 --- a/api/src/utils/types/dto.types.ts +++ b/api/src/utils/types/dto.types.ts @@ -6,17 +6,17 @@ * 2. All derivative works must include clear attribution to the original creator and software, Hexastack and Hexabot, in a prominent location (e.g., in the software's "About" section, documentation, and README file). */ -export enum DtoOperations { +export enum DtoActions { Create = 'create', Read = 'read', Update = 'update', Delete = 'delete', } -export type DtoConfig>> = T; +export type DtoConfig>> = T; export type DtoProps> = { - [K in DtoOperations]?: T[K]; + [K in DtoActions]?: T[K]; }; export type DtoInfer = DTO[K] extends object From 66ba5bff64c8eacbbd7c43be51ca472cc009c606 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 11:23:49 +0100 Subject: [PATCH 10/32] fix: update conversation and user DTO naming --- api/src/chat/dto/conversation.dto.ts | 2 +- api/src/chat/repositories/conversation.repository.ts | 4 ++-- api/src/chat/services/conversation.service.ts | 4 ++-- api/src/user/dto/user.dto.ts | 2 +- api/src/user/repositories/user.repository.ts | 4 ++-- api/src/user/services/user.service.ts | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/src/chat/dto/conversation.dto.ts b/api/src/chat/dto/conversation.dto.ts index 0bb2cb82..19b5fdd2 100644 --- a/api/src/chat/dto/conversation.dto.ts +++ b/api/src/chat/dto/conversation.dto.ts @@ -58,6 +58,6 @@ export class ConversationCreateDto { next?: string[]; } -export type ConversationDTOCruds = DtoConfig<{ +export type ConversationDtoMapActions = DtoConfig<{ create: ConversationCreateDto; }>; diff --git a/api/src/chat/repositories/conversation.repository.ts b/api/src/chat/repositories/conversation.repository.ts index 40429a65..fcf216cd 100644 --- a/api/src/chat/repositories/conversation.repository.ts +++ b/api/src/chat/repositories/conversation.repository.ts @@ -13,7 +13,7 @@ import { Model } from 'mongoose'; import { BaseRepository } from '@/utils/generics/base-repository'; -import { ConversationDTOCruds } from '../dto/conversation.dto'; +import { ConversationDtoMapActions } from '../dto/conversation.dto'; import { Conversation, CONVERSATION_POPULATE, @@ -26,7 +26,7 @@ export class ConversationRepository extends BaseRepository< Conversation, ConversationPopulate, ConversationFull, - ConversationDTOCruds + ConversationDtoMapActions > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/services/conversation.service.ts b/api/src/chat/services/conversation.service.ts index 4f791ea6..f0d36f6a 100644 --- a/api/src/chat/services/conversation.service.ts +++ b/api/src/chat/services/conversation.service.ts @@ -12,7 +12,7 @@ import EventWrapper from '@/channel/lib/EventWrapper'; import { LoggerService } from '@/logger/logger.service'; import { BaseService } from '@/utils/generics/base-service'; -import { ConversationDTOCruds } from '../dto/conversation.dto'; +import { ConversationDtoMapActions } from '../dto/conversation.dto'; import { VIEW_MORE_PAYLOAD } from '../helpers/constants'; import { ConversationRepository } from '../repositories/conversation.repository'; import { Block, BlockFull } from '../schemas/block.schema'; @@ -32,7 +32,7 @@ export class ConversationService extends BaseService< Conversation, ConversationPopulate, ConversationFull, - ConversationDTOCruds + ConversationDtoMapActions > { constructor( readonly repository: ConversationRepository, diff --git a/api/src/user/dto/user.dto.ts b/api/src/user/dto/user.dto.ts index ce706e7d..053414ab 100644 --- a/api/src/user/dto/user.dto.ts +++ b/api/src/user/dto/user.dto.ts @@ -109,6 +109,6 @@ export class UserResetPasswordDto extends PickType(UserCreateDto, [ export class UserRequestResetDto extends PickType(UserCreateDto, ['email']) {} -export type UserDTOCruds = DtoConfig<{ +export type UserDtoMapActions = DtoConfig<{ create: UserCreateDto; }>; diff --git a/api/src/user/repositories/user.repository.ts b/api/src/user/repositories/user.repository.ts index 4b372c17..0bd64de9 100644 --- a/api/src/user/repositories/user.repository.ts +++ b/api/src/user/repositories/user.repository.ts @@ -20,7 +20,7 @@ import { import { BaseRepository } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { UserDTOCruds, UserEditProfileDto } from '../dto/user.dto'; +import { UserDtoMapActions, UserEditProfileDto } from '../dto/user.dto'; import { User, USER_POPULATE, @@ -35,7 +35,7 @@ export class UserRepository extends BaseRepository< User, UserPopulate, UserFull, - UserDTOCruds + UserDtoMapActions > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/user/services/user.service.ts b/api/src/user/services/user.service.ts index 294a822f..06cc641a 100644 --- a/api/src/user/services/user.service.ts +++ b/api/src/user/services/user.service.ts @@ -10,7 +10,7 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; -import { UserDTOCruds } from '../dto/user.dto'; +import { UserDtoMapActions } from '../dto/user.dto'; import { UserRepository } from '../repositories/user.repository'; import { User, UserFull, UserPopulate } from '../schemas/user.schema'; @@ -19,7 +19,7 @@ export class UserService extends BaseService< User, UserPopulate, UserFull, - UserDTOCruds + UserDtoMapActions > { constructor(readonly repository: UserRepository) { super(repository); From ffc260bba26ab53723cf81e870b31b67a159f988 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 11:30:23 +0100 Subject: [PATCH 11/32] fix: implement dynamic create DTO for Block --- api/src/chat/dto/block.dto.ts | 5 +++ api/src/chat/repositories/block.repository.ts | 5 ++- api/src/chat/schemas/block.schema.ts | 40 +++++++++--------- api/src/chat/services/block.service.ts | 8 +++- .../i18n/services/translation.service.spec.ts | 1 + api/src/utils/test/fixtures/block.ts | 41 +++++++++++-------- api/src/utils/test/mocks/block.ts | 2 + 7 files changed, 62 insertions(+), 40 deletions(-) diff --git a/api/src/chat/dto/block.dto.ts b/api/src/chat/dto/block.dto.ts index 7892f424..16a5d9dc 100644 --- a/api/src/chat/dto/block.dto.ts +++ b/api/src/chat/dto/block.dto.ts @@ -21,6 +21,7 @@ import { IsString, } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; import { IsObjectId } from '@/utils/validation-rules/is-object-id'; import { CaptureVar } from '../schemas/types/capture-var'; @@ -146,3 +147,7 @@ export class BlockUpdateDto extends PartialType( @IsOptional() trigger_channels?: string[]; } + +export type BlockDTOMap = DtoConfig<{ + create: BlockCreateDto; +}>; diff --git a/api/src/chat/repositories/block.repository.ts b/api/src/chat/repositories/block.repository.ts index 31b0bacd..27bc42a6 100644 --- a/api/src/chat/repositories/block.repository.ts +++ b/api/src/chat/repositories/block.repository.ts @@ -22,7 +22,7 @@ import { LoggerService } from '@/logger/logger.service'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { BlockCreateDto, BlockUpdateDto } from '../dto/block.dto'; +import { BlockCreateDto, BlockDTOMap, BlockUpdateDto } from '../dto/block.dto'; import { Block, BLOCK_POPULATE, @@ -34,7 +34,8 @@ import { export class BlockRepository extends BaseRepository< Block, BlockPopulate, - BlockFull + BlockFull, + BlockDTOMap > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/schemas/block.schema.ts b/api/src/chat/schemas/block.schema.ts index ab9a0b7a..88d7c0bf 100644 --- a/api/src/chat/schemas/block.schema.ts +++ b/api/src/chat/schemas/block.schema.ts @@ -43,7 +43,7 @@ export class BlockStub extends BaseSchema { validate: isPatternList, default: [], }) - patterns?: Pattern[]; + patterns: Pattern[]; @Prop([ { @@ -52,7 +52,7 @@ export class BlockStub extends BaseSchema { default: [], }, ]) - trigger_labels?: unknown; + trigger_labels: unknown; @Prop([ { @@ -61,19 +61,19 @@ export class BlockStub extends BaseSchema { default: [], }, ]) - assign_labels?: unknown; + assign_labels: unknown; @Prop({ type: Object, default: [], }) - trigger_channels?: string[]; + trigger_channels: string[]; @Prop({ type: Object, default: {}, }) - options?: BlockOptions; + options: BlockOptions; @Prop({ type: Object, @@ -88,13 +88,13 @@ export class BlockStub extends BaseSchema { default: [], }, ]) - nextBlocks?: unknown; + nextBlocks: unknown; @Prop({ type: MongooseSchema.Types.ObjectId, ref: 'Block', }) - attachedBlock?: unknown; + attachedBlock: unknown; @Prop({ type: MongooseSchema.Types.ObjectId, @@ -106,14 +106,14 @@ export class BlockStub extends BaseSchema { type: Boolean, default: false, }) - starts_conversation?: boolean; + starts_conversation: boolean; @Prop({ type: Object, validate: isValidVarCapture, default: [], }) - capture_vars?: CaptureVar[]; + capture_vars: CaptureVar[]; @Prop({ type: Object, @@ -125,22 +125,22 @@ export class BlockStub extends BaseSchema { type: Boolean, default: false, }) - builtin?: boolean; + builtin: boolean; } @Schema({ timestamps: true }) export class Block extends BlockStub { - @Transform(({ obj }) => obj.trigger_labels?.map((elem) => elem.toString())) - trigger_labels?: string[]; + @Transform(({ obj }) => obj.trigger_labels.map((elem) => elem.toString())) + trigger_labels: string[]; - @Transform(({ obj }) => obj.assign_labels?.map((elem) => elem.toString())) - assign_labels?: string[]; + @Transform(({ obj }) => obj.assign_labels.map((elem) => elem.toString())) + assign_labels: string[]; - @Transform(({ obj }) => obj.nextBlocks?.map((elem) => elem.toString())) - nextBlocks?: string[]; + @Transform(({ obj }) => obj.nextBlocks.map((elem) => elem.toString())) + nextBlocks: string[]; @Transform(({ obj }) => obj.attachedBlock?.toString() || null) - attachedBlock?: string; + attachedBlock: string; @Transform(({ obj }) => obj.category.toString()) category: string; @@ -161,16 +161,16 @@ export class BlockFull extends BlockStub { assign_labels: Label[]; @Type(() => Block) - nextBlocks?: Block[]; + nextBlocks: Block[]; @Type(() => Block) - attachedBlock?: Block; + attachedBlock: Block; @Type(() => Category) category: Category; @Type(() => Block) - previousBlocks: Block[]; + previousBlocks?: Block[]; @Type(() => Block) attachedToBlock?: Block; diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index 131f45f5..98ef7e66 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -22,6 +22,7 @@ import { SettingService } from '@/setting/services/setting.service'; import { BaseService } from '@/utils/generics/base-service'; import { getRandom } from '@/utils/helpers/safeRandom'; +import { BlockDTOMap } from '../dto/block.dto'; import { BlockRepository } from '../repositories/block.repository'; import { Block, BlockFull, BlockPopulate } from '../schemas/block.schema'; import { Context } from '../schemas/types/context'; @@ -35,7 +36,12 @@ import { Payload, StdQuickReply } from '../schemas/types/quick-reply'; import { SubscriberContext } from '../schemas/types/subscriberContext'; @Injectable() -export class BlockService extends BaseService { +export class BlockService extends BaseService< + Block, + BlockPopulate, + BlockFull, + BlockDTOMap +> { constructor( readonly repository: BlockRepository, private readonly contentService: ContentService, diff --git a/api/src/i18n/services/translation.service.spec.ts b/api/src/i18n/services/translation.service.spec.ts index 5f0d6bb4..1667a1e8 100644 --- a/api/src/i18n/services/translation.service.spec.ts +++ b/api/src/i18n/services/translation.service.spec.ts @@ -154,6 +154,7 @@ describe('TranslationService', () => { }, }, options: {}, + attachedBlock: null, }; const mockedPlugin: any = { diff --git a/api/src/utils/test/fixtures/block.ts b/api/src/utils/test/fixtures/block.ts index b66611ce..0ddd537b 100644 --- a/api/src/utils/test/fixtures/block.ts +++ b/api/src/utils/test/fixtures/block.ts @@ -8,17 +8,35 @@ import mongoose from 'mongoose'; -import { BlockCreateDto } from '@/chat/dto/block.dto'; -import { BlockModel, Block } from '@/chat/schemas/block.schema'; +import { Block, BlockModel } from '@/chat/schemas/block.schema'; import { CategoryModel } from '@/chat/schemas/category.schema'; import { FileType } from '@/chat/schemas/types/attachment'; import { ButtonType } from '@/chat/schemas/types/button'; import { QuickReplyType } from '@/chat/schemas/types/quick-reply'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; -import { TFixturesDefaultValues } from '../types'; -export const blocks: BlockCreateDto[] = [ +export const fieldsWithDefaultValues = { + options: {}, + nextBlocks: [], + capture_vars: [], + assign_labels: [], + trigger_labels: [], + builtin: false, + starts_conversation: false, + attachedBlock: null, + attachedToBlock: null, +} satisfies Partial; + +type TFieldWithDefaultValues = + | keyof typeof fieldsWithDefaultValues + | keyof BaseSchema; +type TTransformedField = Omit & + Partial>; +type TBlock = TTransformedField; + +export const blocks: TBlock[] = [ { name: 'hasNextBlocks', patterns: ['Hi'], @@ -163,20 +181,9 @@ export const blocks: BlockCreateDto[] = [ }, ]; -export const blockDefaultValues: TFixturesDefaultValues = { - options: {}, - builtin: false, - nextBlocks: [], - capture_vars: [], - assign_labels: [], - trigger_labels: [], - starts_conversation: false, - attachedToBlock: null, -}; - -export const blockFixtures = getFixturesWithDefaultValues({ +export const blockFixtures = getFixturesWithDefaultValues({ fixtures: blocks, - defaultValues: blockDefaultValues, + defaultValues: fieldsWithDefaultValues, }); export const installBlockFixtures = async () => { diff --git a/api/src/utils/test/mocks/block.ts b/api/src/utils/test/mocks/block.ts index 1c8d3c58..4a9da2cc 100644 --- a/api/src/utils/test/mocks/block.ts +++ b/api/src/utils/test/mocks/block.ts @@ -95,6 +95,7 @@ export const baseBlockInstance = { category: undefined, previousBlocks: [], trigger_channels: [], + nextBlocks: [], ...modelInstance, }; @@ -103,6 +104,7 @@ export const blockEmpty: BlockFull = { name: 'Empty', patterns: [], message: [''], + nextBlocks: [], }; // Translation Data From 21b2f46f4c34bcac9401dc4bb6b18ba5241736b4 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 12:02:54 +0100 Subject: [PATCH 12/32] feat: implement dynamic create DTO in the BaseSeeder --- api/src/chat/dto/block.dto.ts | 2 +- api/src/chat/dto/context-var.dto.ts | 6 +++++ api/src/chat/repositories/block.repository.ts | 8 +++++-- .../repositories/context-var.repository.ts | 8 ++++++- api/src/chat/schemas/context-var.schema.ts | 2 +- api/src/chat/seeds/context-var.seed.ts | 8 ++++++- api/src/chat/services/block.service.ts | 4 ++-- api/src/chat/services/context-var.service.ts | 8 ++++++- api/src/utils/generics/base-seeder.ts | 7 +++++- api/src/utils/test/fixtures/contextvar.ts | 22 ++++++++++++++----- 10 files changed, 59 insertions(+), 16 deletions(-) diff --git a/api/src/chat/dto/block.dto.ts b/api/src/chat/dto/block.dto.ts index 16a5d9dc..02ec4f30 100644 --- a/api/src/chat/dto/block.dto.ts +++ b/api/src/chat/dto/block.dto.ts @@ -148,6 +148,6 @@ export class BlockUpdateDto extends PartialType( trigger_channels?: string[]; } -export type BlockDTOMap = DtoConfig<{ +export type BlockDTOMapActions = DtoConfig<{ create: BlockCreateDto; }>; diff --git a/api/src/chat/dto/context-var.dto.ts b/api/src/chat/dto/context-var.dto.ts index 421f6b72..fcef8d64 100644 --- a/api/src/chat/dto/context-var.dto.ts +++ b/api/src/chat/dto/context-var.dto.ts @@ -9,6 +9,8 @@ import { ApiProperty, PartialType } from '@nestjs/swagger'; import { IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; + export class ContextVarCreateDto { @ApiProperty({ description: 'Context var label', type: String }) @IsNotEmpty() @@ -27,3 +29,7 @@ export class ContextVarCreateDto { } export class ContextVarUpdateDto extends PartialType(ContextVarCreateDto) {} + +export type ContextVarDTOMapActions = DtoConfig<{ + create: ContextVarCreateDto; +}>; diff --git a/api/src/chat/repositories/block.repository.ts b/api/src/chat/repositories/block.repository.ts index 27bc42a6..2b33e5b9 100644 --- a/api/src/chat/repositories/block.repository.ts +++ b/api/src/chat/repositories/block.repository.ts @@ -22,7 +22,11 @@ import { LoggerService } from '@/logger/logger.service'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; -import { BlockCreateDto, BlockDTOMap, BlockUpdateDto } from '../dto/block.dto'; +import { + BlockCreateDto, + BlockDTOMapActions, + BlockUpdateDto, +} from '../dto/block.dto'; import { Block, BLOCK_POPULATE, @@ -35,7 +39,7 @@ export class BlockRepository extends BaseRepository< Block, BlockPopulate, BlockFull, - BlockDTOMap + BlockDTOMapActions > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/repositories/context-var.repository.ts b/api/src/chat/repositories/context-var.repository.ts index f2125252..3b7e397e 100644 --- a/api/src/chat/repositories/context-var.repository.ts +++ b/api/src/chat/repositories/context-var.repository.ts @@ -19,11 +19,17 @@ import { Document, Model, Query } from 'mongoose'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; +import { ContextVarDTOMapActions } from '../dto/context-var.dto'; import { ContextVar } from '../schemas/context-var.schema'; import { BlockService } from '../services/block.service'; @Injectable() -export class ContextVarRepository extends BaseRepository { +export class ContextVarRepository extends BaseRepository< + ContextVar, + never, + never, + ContextVarDTOMapActions +> { private readonly blockService: BlockService; constructor( diff --git a/api/src/chat/schemas/context-var.schema.ts b/api/src/chat/schemas/context-var.schema.ts index eb1b77ad..f97e9af9 100644 --- a/api/src/chat/schemas/context-var.schema.ts +++ b/api/src/chat/schemas/context-var.schema.ts @@ -38,7 +38,7 @@ export class ContextVar extends BaseSchema { type: Boolean, default: false, }) - permanent?: boolean; + permanent: boolean; } export const ContextVarModel: ModelDefinition = LifecycleHookManager.attach({ diff --git a/api/src/chat/seeds/context-var.seed.ts b/api/src/chat/seeds/context-var.seed.ts index 965cc571..b654f89d 100644 --- a/api/src/chat/seeds/context-var.seed.ts +++ b/api/src/chat/seeds/context-var.seed.ts @@ -10,11 +10,17 @@ import { Injectable } from '@nestjs/common'; import { BaseSeeder } from '@/utils/generics/base-seeder'; +import { ContextVarDTOMapActions } from '../dto/context-var.dto'; import { ContextVarRepository } from '../repositories/context-var.repository'; import { ContextVar } from '../schemas/context-var.schema'; @Injectable() -export class ContextVarSeeder extends BaseSeeder { +export class ContextVarSeeder extends BaseSeeder< + ContextVar, + never, + never, + ContextVarDTOMapActions +> { constructor(private readonly contextVarRepository: ContextVarRepository) { super(contextVarRepository); } diff --git a/api/src/chat/services/block.service.ts b/api/src/chat/services/block.service.ts index 98ef7e66..42db00a2 100644 --- a/api/src/chat/services/block.service.ts +++ b/api/src/chat/services/block.service.ts @@ -22,7 +22,7 @@ import { SettingService } from '@/setting/services/setting.service'; import { BaseService } from '@/utils/generics/base-service'; import { getRandom } from '@/utils/helpers/safeRandom'; -import { BlockDTOMap } from '../dto/block.dto'; +import { BlockDTOMapActions } from '../dto/block.dto'; import { BlockRepository } from '../repositories/block.repository'; import { Block, BlockFull, BlockPopulate } from '../schemas/block.schema'; import { Context } from '../schemas/types/context'; @@ -40,7 +40,7 @@ export class BlockService extends BaseService< Block, BlockPopulate, BlockFull, - BlockDTOMap + BlockDTOMapActions > { constructor( readonly repository: BlockRepository, diff --git a/api/src/chat/services/context-var.service.ts b/api/src/chat/services/context-var.service.ts index dfcdd164..03baf9f5 100644 --- a/api/src/chat/services/context-var.service.ts +++ b/api/src/chat/services/context-var.service.ts @@ -10,12 +10,18 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; +import { ContextVarDTOMapActions } from '../dto/context-var.dto'; import { ContextVarRepository } from '../repositories/context-var.repository'; import { Block, BlockFull } from '../schemas/block.schema'; import { ContextVar } from '../schemas/context-var.schema'; @Injectable() -export class ContextVarService extends BaseService { +export class ContextVarService extends BaseService< + ContextVar, + never, + never, + ContextVarDTOMapActions +> { constructor(readonly repository: ContextVarRepository) { super(repository); } diff --git a/api/src/utils/generics/base-seeder.ts b/api/src/utils/generics/base-seeder.ts index 139d1d2c..338ac855 100644 --- a/api/src/utils/generics/base-seeder.ts +++ b/api/src/utils/generics/base-seeder.ts @@ -8,6 +8,8 @@ import { FlattenMaps } from 'mongoose'; +import { DtoActions, DtoInfer, DtoProps } from '../types/dto.types'; + import { BaseRepository } from './base-repository'; import { BaseSchema } from './base-schema'; @@ -15,6 +17,7 @@ export abstract class BaseSeeder< T extends FlattenMaps, P extends string = never, TFull extends Omit = never, + DTOCruds extends DtoProps = unknown, > { constructor(protected readonly repository: BaseRepository) {} @@ -27,7 +30,9 @@ export abstract class BaseSeeder< return count === 0; } - async seed(models: Omit[]): Promise { + async seed>( + models: DtoInfer[], + ): Promise { if (await this.isEmpty()) { await this.repository.createMany(models); return true; diff --git a/api/src/utils/test/fixtures/contextvar.ts b/api/src/utils/test/fixtures/contextvar.ts index be325061..8c908271 100644 --- a/api/src/utils/test/fixtures/contextvar.ts +++ b/api/src/utils/test/fixtures/contextvar.ts @@ -8,26 +8,36 @@ import mongoose from 'mongoose'; -import { ContextVarCreateDto } from '@/chat/dto/context-var.dto'; -import { ContextVarModel, ContextVar } from '@/chat/schemas/context-var.schema'; +import { ContextVar, ContextVarModel } from '@/chat/schemas/context-var.schema'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; -const contextVars: ContextVarCreateDto[] = [ +export const fieldsWithDefaultValues = { + permanent: false, +} satisfies Partial; + +type TFieldWithDefaultValues = + | keyof typeof fieldsWithDefaultValues + | keyof BaseSchema; +type TTransformedField = Omit & + Partial>; +type TContextVar = TTransformedField; + +const contextVars: TContextVar[] = [ { label: 'test context var 1', name: 'test1', - permanent: false, }, { label: 'test context var 2', name: 'test2', - permanent: false, }, ]; -export const contextVarFixtures = getFixturesWithDefaultValues({ +export const contextVarFixtures = getFixturesWithDefaultValues({ fixtures: contextVars, + defaultValues: fieldsWithDefaultValues, }); export const installContextVarFixtures = async () => { From 9e49e8256e98c3de1fb4769d71e77a9d05011cac Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 14:52:38 +0100 Subject: [PATCH 13/32] feat: implement dynamic create DTO for Category --- api/src/chat/dto/category.dto.ts | 10 +++++-- .../chat/repositories/category.repository.ts | 8 ++++- api/src/chat/schemas/category.schema.ts | 6 ++-- api/src/chat/seeds/category.seed.ts | 8 ++++- api/src/chat/services/category.service.ts | 8 ++++- api/src/utils/test/fixtures/category.ts | 30 +++++++++++-------- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/api/src/chat/dto/category.dto.ts b/api/src/chat/dto/category.dto.ts index ca896e22..0a857d6d 100644 --- a/api/src/chat/dto/category.dto.ts +++ b/api/src/chat/dto/category.dto.ts @@ -8,14 +8,16 @@ import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; import { + IsArray, IsBoolean, IsNotEmpty, + IsNumber, IsOptional, IsString, - IsNumber, - IsArray, } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; + export class CategoryCreateDto { @ApiProperty({ description: 'Category label', type: String }) @IsNotEmpty() @@ -39,3 +41,7 @@ export class CategoryCreateDto { } export class CategoryUpdateDto extends PartialType(CategoryCreateDto) {} + +export type CategoryDTOMapActions = DtoConfig<{ + create: CategoryCreateDto; +}>; diff --git a/api/src/chat/repositories/category.repository.ts b/api/src/chat/repositories/category.repository.ts index 45170306..f27f05ac 100644 --- a/api/src/chat/repositories/category.repository.ts +++ b/api/src/chat/repositories/category.repository.ts @@ -14,11 +14,17 @@ import { Document, Model, Query } from 'mongoose'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; +import { CategoryDTOMapActions } from '../dto/category.dto'; import { Category } from '../schemas/category.schema'; import { BlockService } from '../services/block.service'; @Injectable() -export class CategoryRepository extends BaseRepository { +export class CategoryRepository extends BaseRepository< + Category, + never, + never, + CategoryDTOMapActions +> { private readonly blockService: BlockService; constructor( diff --git a/api/src/chat/schemas/category.schema.ts b/api/src/chat/schemas/category.schema.ts index 7ed8cdf3..bc7b8cfd 100644 --- a/api/src/chat/schemas/category.schema.ts +++ b/api/src/chat/schemas/category.schema.ts @@ -25,19 +25,19 @@ export class Category extends BaseSchema { type: Boolean, default: false, }) - builtin?: boolean; + builtin: boolean; @Prop({ type: Number, default: 100, }) - zoom?: number; + zoom: number; @Prop({ type: [Number, Number], default: [0, 0], }) - offset?: [number, number]; + offset: [number, number]; } export const CategoryModel: ModelDefinition = LifecycleHookManager.attach({ diff --git a/api/src/chat/seeds/category.seed.ts b/api/src/chat/seeds/category.seed.ts index d1917309..034f9623 100644 --- a/api/src/chat/seeds/category.seed.ts +++ b/api/src/chat/seeds/category.seed.ts @@ -10,11 +10,17 @@ import { Injectable } from '@nestjs/common'; import { BaseSeeder } from '@/utils/generics/base-seeder'; +import { CategoryDTOMapActions } from '../dto/category.dto'; import { CategoryRepository } from '../repositories/category.repository'; import { Category } from '../schemas/category.schema'; @Injectable() -export class CategorySeeder extends BaseSeeder { +export class CategorySeeder extends BaseSeeder< + Category, + never, + never, + CategoryDTOMapActions +> { constructor(private readonly categoryRepository: CategoryRepository) { super(categoryRepository); } diff --git a/api/src/chat/services/category.service.ts b/api/src/chat/services/category.service.ts index 079a3a7e..8bb4472e 100644 --- a/api/src/chat/services/category.service.ts +++ b/api/src/chat/services/category.service.ts @@ -10,11 +10,17 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; +import { CategoryDTOMapActions } from '../dto/category.dto'; import { CategoryRepository } from '../repositories/category.repository'; import { Category } from '../schemas/category.schema'; @Injectable() -export class CategoryService extends BaseService { +export class CategoryService extends BaseService< + Category, + never, + never, + CategoryDTOMapActions +> { constructor(readonly repository: CategoryRepository) { super(repository); } diff --git a/api/src/utils/test/fixtures/category.ts b/api/src/utils/test/fixtures/category.ts index 2bb0f6c5..479b1b99 100644 --- a/api/src/utils/test/fixtures/category.ts +++ b/api/src/utils/test/fixtures/category.ts @@ -8,13 +8,25 @@ import mongoose from 'mongoose'; -import { CategoryCreateDto } from '@/chat/dto/category.dto'; -import { CategoryModel, Category } from '@/chat/schemas/category.schema'; +import { Category, CategoryModel } from '@/chat/schemas/category.schema'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; -import { TFixturesDefaultValues } from '../types'; -export const categories: CategoryCreateDto[] = [ +export const fieldsWithDefaultValues = { + builtin: false, + zoom: 100, + offset: [0, 0], +} satisfies Partial; + +type TFieldWithDefaultValues = + | keyof typeof fieldsWithDefaultValues + | keyof BaseSchema; +type TTransformedField = Omit & + Partial>; +type TCategory = TTransformedField; + +export const categories: TCategory[] = [ { label: 'test category 1', }, @@ -23,15 +35,9 @@ export const categories: CategoryCreateDto[] = [ }, ]; -export const categoryDefaultValues: TFixturesDefaultValues = { - builtin: false, - zoom: 100, - offset: [0, 0], -}; - -export const categoryFixtures = getFixturesWithDefaultValues({ +export const categoryFixtures = getFixturesWithDefaultValues({ fixtures: categories, - defaultValues: categoryDefaultValues, + defaultValues: fieldsWithDefaultValues, }); export const installCategoryFixtures = async () => { From dc7d451378914800e2ab5f714866f073ee4c3b15 Mon Sep 17 00:00:00 2001 From: yassinedorbozgithub Date: Fri, 10 Jan 2025 15:59:02 +0100 Subject: [PATCH 14/32] feat: implement dynamic create DTO for Label --- api/src/chat/dto/label.dto.ts | 8 ++++++- api/src/chat/repositories/label.repository.ts | 4 +++- api/src/chat/schemas/label.schema.ts | 4 ++-- api/src/chat/services/label.service.ts | 8 ++++++- api/src/utils/test/fixtures/label.ts | 24 ++++++++++++------- 5 files changed, 34 insertions(+), 14 deletions(-) diff --git a/api/src/chat/dto/label.dto.ts b/api/src/chat/dto/label.dto.ts index 50107959..8db0938b 100644 --- a/api/src/chat/dto/label.dto.ts +++ b/api/src/chat/dto/label.dto.ts @@ -9,12 +9,14 @@ import { ApiProperty, ApiPropertyOptional, PartialType } from '@nestjs/swagger'; import { IsNotEmpty, + IsObject, IsOptional, IsString, Matches, - IsObject, } from 'class-validator'; +import { DtoConfig } from '@/utils/types/dto.types'; + export class LabelCreateDto { @ApiProperty({ description: 'Label title', type: String }) @IsNotEmpty() @@ -39,3 +41,7 @@ export class LabelCreateDto { } export class LabelUpdateDto extends PartialType(LabelCreateDto) {} + +export type LabelDtoMapActions = DtoConfig<{ + create: LabelCreateDto; +}>; diff --git a/api/src/chat/repositories/label.repository.ts b/api/src/chat/repositories/label.repository.ts index 5794c9d3..13d67ce1 100644 --- a/api/src/chat/repositories/label.repository.ts +++ b/api/src/chat/repositories/label.repository.ts @@ -14,6 +14,7 @@ import { Document, Model, Query } from 'mongoose'; import { BaseRepository, DeleteResult } from '@/utils/generics/base-repository'; import { TFilterQuery } from '@/utils/types/filter.types'; +import { LabelDtoMapActions } from '../dto/label.dto'; import { Label, LABEL_POPULATE, @@ -26,7 +27,8 @@ import { export class LabelRepository extends BaseRepository< Label, LabelPopulate, - LabelFull + LabelFull, + LabelDtoMapActions > { constructor( readonly eventEmitter: EventEmitter2, diff --git a/api/src/chat/schemas/label.schema.ts b/api/src/chat/schemas/label.schema.ts index 592d99e2..41709703 100644 --- a/api/src/chat/schemas/label.schema.ts +++ b/api/src/chat/schemas/label.schema.ts @@ -43,13 +43,13 @@ export class LabelStub extends BaseSchema { @Prop({ type: String, }) - description?: string; + description: string; @Prop({ type: Boolean, default: false, }) - builtin?: boolean; + builtin: boolean; } @Schema({ timestamps: true }) diff --git a/api/src/chat/services/label.service.ts b/api/src/chat/services/label.service.ts index e5db9cef..b4833ee2 100644 --- a/api/src/chat/services/label.service.ts +++ b/api/src/chat/services/label.service.ts @@ -10,11 +10,17 @@ import { Injectable } from '@nestjs/common'; import { BaseService } from '@/utils/generics/base-service'; +import { LabelDtoMapActions } from '../dto/label.dto'; import { LabelRepository } from '../repositories/label.repository'; import { Label, LabelFull, LabelPopulate } from '../schemas/label.schema'; @Injectable() -export class LabelService extends BaseService { +export class LabelService extends BaseService< + Label, + LabelPopulate, + LabelFull, + LabelDtoMapActions +> { constructor(readonly repository: LabelRepository) { super(repository); } diff --git a/api/src/utils/test/fixtures/label.ts b/api/src/utils/test/fixtures/label.ts index c38f5215..bea7ff47 100644 --- a/api/src/utils/test/fixtures/label.ts +++ b/api/src/utils/test/fixtures/label.ts @@ -8,13 +8,23 @@ import mongoose from 'mongoose'; -import { LabelCreateDto } from '@/chat/dto/label.dto'; import { Label, LabelModel } from '@/chat/schemas/label.schema'; +import { BaseSchema } from '@/utils/generics/base-schema'; import { getFixturesWithDefaultValues } from '../defaultValues'; -import { TFixturesDefaultValues } from '../types'; -export const labels: LabelCreateDto[] = [ +export const fieldsWithDefaultValues = { + builtin: false, +} satisfies Partial