Giter Club home page Giter Club logo

dataui-nestjs-crud's Issues

@Crud dynamic params and ManyToOne Relationships incorrect column mapping

Hi everyone,

First of all, I'd like to extend my gratitude to the maintainers for your efforts in keeping this project both usable and up-to-date.

I'm encountering an issue when using the @Crud controller's params option, specifically when it involves ManyToOne relationships. Below is a description of the problem and the technical context:

Dependencies:

"dependencies": {
    "@dataui/crud": "^5.3.3",
    "@dataui/crud-typeorm": "^5.3.3",
    "@nestjs/common": "10.3.7",
    "@nestjs/config": "3.2.2",
    "@nestjs/core": "10.3.7",
    "@nestjs/platform-express": "10.3.7",
    "@nestjs/swagger": "7.3.1",
    "@nestjs/typeorm": "10.0.2",
    "class-transformer": "0.5.1",
    "class-validator": "0.14.0",
    "pg": "8.11.2",
    "reflect-metadata": "0.1.13",
    "typeorm": "0.3.20"
  }

Relevant Code:

import { Crud, CrudController } from '@dataui/crud';
import { Controller } from '@nestjs/common';
import { ArticleService } from './article.service';
import { Article } from './entities/article.entity';

@Controller('/author/:authorId/article')
@Crud({
  model: {
    type: Article,
  },
  params: {
    authorId: {
      field: 'author',
      type: 'number',
    },
  },
})
export class ArticleController implements CrudController<Article> {
  constructor(public service: ArticleService) {}
}
import { Author } from 'src/author/entities/author.entity';
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Article {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @ManyToOne(() => Author, {
    nullable: false,
  })
  author: Author;
}
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity()
export class Author {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;
}

Issue:

When I make a GET request to /author/1/article, I encounter a 500 error with the following log:

error: error: column Article.authorId.id does not exist
ERROR [ExceptionsHandler] column Article.authorId.id does not exist
QueryFailedError: column Article.authorId.id does not exist
at PostgresQueryRunner.query (./api/src/driver/postgres/PostgresQueryRunner.ts:331:19)
at processTicksAndRejections (node:internal/process/task_queues:95:5)
at SelectQueryBuilder.loadRawResults (./api/src/query-builder/SelectQueryBuilder.ts:3805:25)
at SelectQueryBuilder.executeEntitiesAndRawResults (./api/src/query-builder/SelectQueryBuilder.ts:3551:26)
at SelectQueryBuilder.getRawAndEntities (./api/src/query-builder/SelectQueryBuilder.ts:1670:29)
at SelectQueryBuilder.getMany (./api/src/query-builder/SelectQueryBuilder.ts:1760:25)

The generated SQL query:

query failed: SELECT "Article"."id" AS "Article_id", "Article"."title" AS "Article_title", "Article"."authorId" FROM "article" "Article" WHERE ("Article"."authorId.id" = $1) -- PARAMETERS: [1]

Further Investigation:

I discovered that the TypeOrmCrudService utilizes the getFieldWithAlias function, potentially mishandling the mapping of properties to database columns. This function relies on entityColumnsHash, which is initialized using TypeORM's column metadata in onInitMapEntityColumns. Intriguingly, TypeORM provides a databasePath = 'authorId.id', which doesn't seem correct.

image

Modifying the initialization line in the TypeOrmCrudService to use databaseName instead of databasePath appears to solve the issue:

this.entityColumnsHash[prop.propertyName] = prop.databaseName;

Although this adjustment resolves the immediate problem, I'm uncertain if it's the correct fix, or if the underlying issue might be within TypeORM itself.

I would appreciate any help or comments

Join Condition (on clause) not working as given in the docs

As given in the docs when sending the query string like
http://localhost:3100/Customers?join=facilities||id,status||on[0]=facilities.status||$eq||Archived&filter[0]=id||$eq||4 the join condition gets omitted because the join key is not intercepted correctly, key value pair is coming as
key= join=facilities||id,status||on
value= facilities.status||$eq||Archived

But we actually need
key= join
value= facilities||id,status||on[0]=facilities.status||$eq||Archived

Warning: Failed to parse source map

We get this warning in many files, is this some option we could use to fix it or it needs a change in this package?

WARNING in ./node_modules/@dataui/crud-request/lib/interfaces/create-query-params.interface.js
Module Warning (from ./node_modules/source-map-loader/dist/cjs.js):
Failed to parse source map from 'D:\R\project-frontend\node_modules\@dataui\crud-request\src\interfaces\create-query-params.interface.ts' file: Error: ENOENT: no such file or directory, open 'D:\R\project-front
end\node_modules\@dataui\crud-request\src\interfaces\create-query-params.interface.ts'

Thanks

Tips: How to add customized filter conditions from backend service.

Sometimes your system need to add filter conditions after the HTTP request parsed.
For example you would like to filter records by current login user ID.

You would be like to do it as following:

public async getRecordsByUserId(req: CrudRequest, userId: number) {
    req.parsed.join.push({
      field: 'user',
    });
    const builder = await this.createBuilder(req.parsed, req.options, true);
    builder.andWhere('User.id = :userId', { userId });
    return this.doGetMany(builder, req.parsed, req.options);
}

[help] how to add additional methods without overriding

I want to add additional methods that are typically similar to the existing ones.

for example, I want to add /me that is similar to getManyBase and getOneBase, but also keep / that getManyBase offers.
but also, I want it to simulate getOneBase with minor changes.
the only way I found till now is to manually implement it from scratch

export class UsersController implements CrudController<UserEntity>{
constructor( ... ){}


// without any modifications, this should be same as `/`
@GET("/me")
getUserInfo(req){
 req.user = getCurrentuser()
 return this.base.getOneBase(req)
}
}

Minor typo in main.yml: Docker Compose command

Description

This issue reports a minor typo in the main.yml file. The command used to start Docker Compose services has an extra hyphen before docker compose.

Current Code:

run: docker-compose up -d

Correct Code:

run: docker compose up -d

The hyphen before docker compose is unnecessary and might lead to errors when executing the command. Removing this hyphen ensures the command runs smoothly.

Impact:

This typo could potentially cause the docker-compose up -d command to fail, preventing the intended action of starting all Docker Compose services in detached mode.

Proposed Fix:

Simply remove the hyphen before docker compose in the main.yml file.

extend CrudController

we can implement the interface CrudController<Entity>, is there a way to provide a class that extends CrudController instead of the original one?

CrudConfigService.load({
CrudController: ExtendedCrudController
})
class ExtendedCrudController extends CrudController{

additionalMethod(req){
  return this.service.something();
}

}

sure we can create a class from scratch that implements CrudController, but in this case we'll need to implement all methods from scratch, unless the class itself is exposed

// suppose that there is a class named CrudControllerClass is exposed by this library
class ExtendedCrudController extends CrudControllerClass{}

// and in the controller
class MyController extends ExtendedCrudController{}

empty schema for the Entity

after setup @crud() with typescript as mentioned in the docs, I opened the swagger UI to try out the routes that are created by this package.
it works, but I noticed that the schema is always empty

image

solution:
solved by adding @nestjs/swagger to plugins

nest-cli.json

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "plugins": [
      {
        "name": "@nestjs/swagger",
        "options": {
          "introspectComments": true,
          "classValidatorShim": true
        }
      }
    ]
  }
}

Wrong repo on npm

First of all, thanks a lot for this fork and for keeping the lib alive, been using it since the guy from original library disappeared. The issue is that npm links to the wrong repo and so finding this one is really hard. The only direct link is in the issues for original lib.

need customised Auth

some time we need some endpoint public and some with Auth then how we can handle this

How to implement ManyToMany?

I have two Entities, but I don't know how to implement ManyToMany in Curd
Category Entity

import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class Category {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string
}

Question Entity

import {
    Entity,
    PrimaryGeneratedColumn,
    Column,
    ManyToMany,
    JoinTable,
} from "typeorm"
import { Category } from "./Category"

@Entity()
export class Question {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string

    @Column()
    text: string

    @ManyToMany(() => Category)
    @JoinTable()
    categories: Category[]
}

How can I insert, update, find, and delete ManyToMany in Crud?
I haven't found a good solution at the moment🙁

include soft deleted rows

I want to include the soft deleted rows for all getOneBase operations, but not getManyBase

CrudConfigService.load({
getOneBase: {
  query:{
      withDeleted: true,
  }
}
})

also, ...

@Override()
getOne(@parsedReq() req: any){
   let options = {  withDeleted: true, }
  return this.base.getOneBase(req, options)
}

creating a new custom method doesn't give the same signature as the inherited getOneBase

@GET()
findOne(@Req() req: any){
 // won't work as expected, and all queries must be implemented manually, such as filter, ...
 // there is no exposed functionality to convert req into parsed req, even `(@ParsedReq() req)` gives undefined
 return this.base.getOneBase(req)
}

Fail to compile on last stable version 5.1.3

  • Error message on compilation step :
node_modules/@dataui/crud/lib/interfaces/auth-options.interface.d.ts(2,31): error TS2307: Cannot find module '@dataui/crud-util/src' or its corresponding type declarations.
import { SCondition } from '@dataui/crud-request/lib/types/request-query.types';
import { ObjectLiteral } from 'crud-util/src';

export interface AuthGlobalOptions {
  property?: string;
}

export interface AuthOptions {
  property?: string;
  filter?: (req: any) => SCondition | void;
  or?: (req: any) => SCondition | void;
  persist?: (req: any) => ObjectLiteral;
}

to

import { SCondition } from '@dataui/crud-request/lib/types/request-query.types';
import { ObjectLiteral } from '@dataui/crud-util/lib/types/object-literal.type';
export interface AuthGlobalOptions {
    property?: string;
}
export interface AuthOptions {
    property?: string;
    filter?: (req: any) => SCondition | void;
    or?: (req: any) => SCondition | void;
    persist?: (req: any) => ObjectLiteral;
}

Soft Delete Support (TypeORM)

Any change to add this function by default on CRUD config?

Something like that:

@Crud({
  model: {
    type: MyModel,
  },
  routes: {
    deleteOneBase: {
      softDelete: true,
    },
  },
})
@Controller('my-models')
export class MyModelController implements CrudController<MyModel> {
    constructor(public service: MyModelService) {}
}

cannot set decorators globally

public.decorator.ts

export const Public = () => SetMetadata('isPublicEndPoint', true);

main.ts

import { CrudConfigService } from '@dataui/crud';
CrudConfigService.load({
  routes: {
    getOneBase: { decorators: [Public()] }
  },

});
import { AppModule } from './app.module';
import { Public } from './public.decorator.ts'

error:

 getOneBase: { decorators: [(0, auth_decorator_1.Public)()] },
                                                        ^
ReferenceError: Cannot access 'auth_decorator_1' before initialization

solution
solved by importing Public before CrudConfigService.load()

wrong typing

in docs there is an example to override a method like this

  get base(): CrudController<Hero> {
    return this;
  }

  @Override()
  getMany(
    @ParsedRequest() req: CrudRequest,
  ) {
    return this.base.getManyBase(req);
  }

however, the base methods such as getManyBase is typed to return a promise or undefined
so, the previous snippet makes TS to give this error

Cannot invoke an object which is possibly 'undefined'.ts(2722)

we can make an ugly workaround and edit our code to be

 return this.base.getManyBase?.(req);

this also makes TS complain again because the overrides method i.e. getMany() should return Promise only, not promise or undefined

transform the body without interceptors

I want to transform the body before creating a resource.
I know we can use interceptors or overriding the crud methods, but creating an interceptor class for each route is overkill and harder than implementing the crud from scratch.

there is a lighter solution, a transform function that takes the body (or maybe the request) and returns the new body (or request)

@Crud({
  model: { ... },
  routes: {
   createOneBase: {
    // or transform(req) => newReq
     transform(body: MyEntity ) => ({ ...body, status:"ok" })
   }
}
})

QueryFilterFunction receives undefined for search

This is an issue from the original repository but still relevant, so I am reposting the issue here.

Original issue: nestjsx#551

When declaring the filter option inside of the @Crud() decorator for a controller it is only working when provided as a static object. When I use the documented QueryFilterFunction, as described [in the docs (https://github.com/nestjsx/crud/wiki/Controllers#filter), the first parameter is receiving undefined and all filters are ignored.

I need to extend any incoming filters with some persisted filters based on the current date for the records. Using new Date() in the static object generates a matching static date-value and therefore breaks my intent. The solution makes sense to use a factory function to merge the search options, but so long as the incoming ```SCondition```` is empty that is impossible.

I'm using the latest releases for the following libraries.

"@nestjs/typeorm": "~7.1.0",
"@nestjsx/crud": "~4.6.2",
"@nestjsx/crud-typeorm": "~4.6.2",
"typeorm": "~0.2.25"

Is it possible to cascade soft delete with this library?

As far as I understand to make soft delete cascade possible in TYPEORM we need to set { cascade : true } in children
After that when we delete the parent => children will be also deleted

The thing is that to make it work with soft delete we need to pass entity with its children to soft delete method
But when I add join to the DELETE endpoint join does not happening, even if i add eager join.

So how do you handle cascade soft delete with this lib?

P.S (using TYPEORM)

export helpers and expose crudSwagger decorator

export helper files in index.ts, so we can create a custom decorator to add Swagger props to custom routes.

also, this package can expose CrudSwagger() decorator to add similar swagger props to that used by @Crud()

  • replace export * from './crud/crud-routes.factory'; with export * from './crud';

  • expose CrudSwagger() decorator

usage:

@Crud(...)
export class MyController implements implements CrudController<MyEntity>{

@Get("me")
@CrudSwagger()
customRoute(){
 return this.service.getUserInfo()
}

}

Bug: GetMany with join + pagination throws duplicate column error

Bug Report

I'm encountering an error when using getMany with joins and pagination. The issue seems to be related to duplicate column names in the query results.

  • Context: Using getMany method in NestJS CRUD library.
  • Problem: Duplicate column names appear in the query results when using joins and pagination together.
  • Impact: This might lead to unexpected behavior and errors in your application.

withDeleted not working with relations

When using with deleted from the query builder

.setIncludeDeleted(1)

the relations included via @crud decorator are not being fetched if they have been deleted.

This is because builder.withDeleted(); is being called after the joins are created.

[EVOLUTION] - Store auth information on CrudRequest object

Hi.

First of all : thinks you for your fork ! ;-)


  • Subject :

    • CrudRequest object is pass between Controller and Service. But on this object we dont store any context informations as authenticate user for exemple : (this is nice to have auth user object on service (stored on native Request object))
  • Do you think it's possible to store extra informations on CrudRequest object and in particular auth. user?

    • Note :
      • Store native NestJs Request object is not possible : risk of producing strange case with singleton service (switch to Scope=Request by nestJs), but store auth user information seen to be possible.
      • Auth user can be found on native Request object with nesths-crud configuration auth. sample configuration ;
CrudConfigService.load({
      params: {
       [...]
      },
      auth: {
        property: 'user',
      },
[...]
* CurdRequest interface can became : 
export interface CrudRequest {
    parsed: ParsedRequestParams;
    options: CrudRequestOptions;
    auth: any; // according to configuration
    extra: any; // any params for business logic
}

access to Query Params from custom endpoint

Is there a way to use CrudRequest in a custom endpoint and to have access in Query Params and their functionality? For example to have something like that:

custom endpoint: /something/3/stats?filter[0]=price||$lte||100

@Get('/:id/stats')
async getStatistics(
  @Param('id') id:number,
  @ParsedRequest() req: CrudRequest
) {
  . . .
}

and to have access to these Query Params and the way they works:
image
or how to extend the functionality that the Query Params have?

Feature Request: Join Filter (ON clause)

Current Functionality:

NestJS CRUD currently allows for joining related entities in queries. However, there is limited control over the join condition itself.

Request:

This feature request proposes the addition of join filters, allowing users to specify a WHERE condition within the ON clause of a join.

Benefits:

  • Increased flexibility in querying related data.
  • Ability to create more complex and specific queries involving joins.

Implementation:

Introduce a new property within the QueryJoin type of crud-request. This property could be named on and accept array of objects of type QueryFilter representing the WHERE condition for the join.

Example:

export declare type QueryJoin = {
  field: string;
  select?: QueryField[];
  on?: QueryFilter[]; // New property
};

This functionality would allow users to specify conditions within the join, like so:

query.setJoin({
    field: 'author',
    select: ['name', 'image'],
    on: [{ field: 'author.id', operator: 'eq', value: 123 }], // Filter by specific author ID
});

By implementing join filters, dataui-nestjs-crud would provide greater control over join conditions, enabling more sophisticated data retrieval scenarios.

wrong Reflect method

in this line

      Reflect.defineMetadata(metadataKey, metadataValue, target, propertyKey);

Reflect doesn't have a static method called defineMetadata, it is defined by reflect-metadata which must be imported as a side effect like this:

import "reflect-metadata";

//Now we can use  Reflect.defineMetadata()

the file uses it doesn't import reflect-metadata though it is add to dependencies

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.