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;