TriPSs / nestjs-query

Easy CRUD for GraphQL.
https://tripss.github.io/nestjs-query/
MIT License
152 stars 43 forks source link

How to correctly extend CreateResolver? #157

Closed dipeshba closed 9 months ago

dipeshba commented 1 year ago

I am trying to write a custom CreateOne Mutation for one of the entities. What is the way to do that? Looking at documentation https://tripss.github.io/nestjs-query/docs/graphql/resolvers#createresolver it't not very clear how can i do that? Can this be described a little more?

TriPSs commented 1 year ago

Could you explain a bit more how your situation is and what you want? Also see custom endpoints on how to create a custom endpoint (I do see we did not mention mutations here but it's very similar), also a bit more info here

dipeshba commented 1 year ago

So basically, I have an Item entity and whenever an item is created, i want to create few other new related entities (also probably run a TypeOrm transaction). I thought of extending TypeOrmQueryService as mentioned here https://tripss.github.io/nestjs-query/docs/persistence/typeorm/custom-service . But createOne function doesn't seems to receive Item Input DTO and instead gets the Item entity itself, which means i can't pass any field that or not in Item entity, but in ItemInputDTO i have created.

Here is what i though of doing...

import { QueryService } from '@ptc-org/nestjs-query-core'
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Item } from './entities/item.entity';

@QueryService(Item)
export class ItemService extends TypeOrmQueryService<Item> {
  constructor(
    @InjectRepository(Item) repo: Repository<Item>,
  ) {
    super(repo);
  }

  createOne(record: Item): Promise<Item> {

    // TO DO: Call item repository to save item

  }
}
TriPSs commented 1 year ago

Okay so to double check, the TodoItemService is added as ServiceClass to the module? You could then indeed overwrite the createOne like this:

  public async createOne(record: DeepPartial<Item>): Promise<Item> {
    const createdRecord = await super.createOne(record)
     // Do your stuff with it 
    return createdRecord
  }
dipeshba commented 1 year ago

Only issue here is, record: DeepPartial<Item> I would like to receive ItemInputDTO as there are some additional fields in it, that don't directly correspond to Item entity.

TriPSs commented 1 year ago

Are you than maybe looking for @BeforeCreateOne?

Would like to point out that the interface maybe says you get DeepPartial<Item> but you actually get CreateOneInputType<Item>

dipeshba commented 1 year ago

Probably do something like this:

Are you than maybe looking for @BeforeCreateOne?

I checked this as well. But this seems to be useful if i just want to modify something in DTO not create return it back? You think i can use it to create and save new item?

Would like to point out that the interface maybe says you get DeepPartial<Item> but you actually get CreateOneInputType<Item>

So you mean to say I can do?

createOne(input: CreateOneInputType<CreateItemInputDto> ): Promise<Item> {
      // Do my stuff and return Item entity
 }
TriPSs commented 1 year ago

That should be correct :)

dipeshba commented 1 year ago

That should do job then! Worth to add this in documentation. I will give this a try and update :) Really thanks for your help on this!

dipeshba commented 1 year ago

Not sure if this works properly. I get the IDE error. Attached screenshot and the code below:

import { QueryService } from '@ptc-org/nestjs-query-core'
import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Item } from './entities/item.entity';
import { CreateOneInputType } from '@ptc-org/nestjs-query-graphql';
import { CreateItemInputDto } from './dto/create-item-input.dto';
import _ from 'lodash';
import { Sku } from 'src/skus/entities/sku.entity';

@QueryService(Item)
export class ItemService extends TypeOrmQueryService<Item> {
    constructor(
        @InjectRepository(Item)
        private itemRepository: Repository<Item>,
        @InjectRepository(Sku)
        private skuRepository: Repository<Sku>,
    ) {
        super(itemRepository);
    }

    public async createOne(input: CreateOneInputType<CreateItemInputDto>): Promise<Item> {
        const item = this.itemRepository.create({
            name: input.input.name,
            description: input.input.description,
            measuringUnitId: input.input.measuringUnitId,
            skus: [
                this.skuRepository.create({
                    quantity: input.input.quantity,
                    skuId: input.input.skuId || _(input.input.name).kebabCase().toUpperCase(),
                })
            ]
        }
        )
        return this.itemRepository.save(item);
    }

}
Screenshot 2023-07-24 at 12 56 56 PM
TriPSs commented 1 year ago

I think you need to update this:

    public async createOne(input: CreateOneInputType<CreateItemInputDto>): Promise<Item> {

To

    public async createOne(input: CreateItemInputDto): Promise<Item> {

Or

    public async createOne(input: DeepPartial<Item>): Promise<Item> {
TriPSs commented 9 months ago

Closing, if still an issue please reopen.

abrenoch commented 5 months ago

Hey I think I'm having trouble with this.

I'm trying to add an additional field to the 'createOneX' input that is not stored with the entity:

import { Field, InputType } from '@nestjs/graphql';
import { CreateOneInputType } from '@ptc-org/nestjs-query-graphql';

import { AssetDTO } from '../asset.dto';

@InputType('CreateOneAssetInput')
export class CreateOneAssetInputDTO extends CreateOneInputType(
  'asset',
  AssetDTO,
) {
  @Field(() => String)
  filePath: string;
}

filePath is used to create or lookup an associated entry, making my graphql schema look like this:

type CreateOneAssetInput {
  asset: CreateAsset!
  filePath: String!
}

The query service looks like:

export class AssetQueryService extends ProxyQueryService<
  AssetEntity,
  CreateOneAssetInputDTO
> {
  fileRepository: Repository<FileEntity>;

  constructor(
    @InjectQueryService(AssetEntity)
    service: QueryService<AssetEntity, CreateOneAssetInputDTO>,
    @InjectRepository(FileEntity)
    fileRepository: Repository<FileEntity>,
  ) {
    super(service);
    this.fileRepository = fileRepository;
  }

  async createOne(input: CreateOneAssetInputDTO): Promise<AssetEntity> {
    const { filePath } = input;

    const file = await this.fileRepository
      .createQueryBuilder()
      .insert()
      .into(FileEntity)
      .orIgnore()
      .values({
        location: filePath,
      })
      .updateEntity(false)
      .execute();

    // ... more stuff

    return this.proxied.createOne(input);
  }
}

The problem I'm having is input: CreateOneAssetInputDTO in createOne is delivered as AssetDTO rather than the whole input argument ({ asset: ..., filePath: ... }).

Am I going about this the right way? As always your help is appreciated!