Skip to content

Recipes - Search command

If you need to perform complex queries, read operations may not suffice.

You can create a search command to use the full potential of your Database / ORM.

Setup

Start by generating a new cmd using the CLI.

eicrud generate cmd profile search

Let's change the DTO to accept a string to be used as a regex:

search.dto.ts
export class SearchDto {
  @IsString()
  @$Transform((v: string) => v.replace(/[.*+?^$}{)(|[\]\\]/g, '\\$&'))
  userNameLike: string;

  @IsOptional()
  @IsString()
  visibility?: 'public' | 'private';
}

export type SearchReturnDto = any;

Note

We escape userNameLike of any regex character to avoid potential regex attacks (ReDoS).

Then, let's allow guests to use the search command in the security:

search.security.ts
const getCmdSecurity = (search, profile): CmdSecurity => { 
    return {
        dto: SearchDto,
        rolesRights: {
            guest: {
                async defineCMDAbility(can, cannot, ctx) {
                    // Define abilities for guest
                    can(search, profile);
                }
            }
        },
    }
}
Alternatively, you can use the guestCanUseAll parameter for better performance.
search.security.ts
const getCmdSecurity = (search, profile): CmdSecurity => { 
    return {
        dto: SearchDto,
        rolesRights: {},
        guestCanUseAll: true
    }
}

Implementation

We can now implement the command action, to make things simpler we use the CrudService->$find method.

We're using a simple regex to search on the username field, but you can use any of MikroOrm's operators like $and & $or.

search.action.ts
export async function search(this: ProfileService, dto: SearchDto, ctx: CrudContext, inheritance?: any ){
    const query: Partial<Profile> = {
      userName: new RegExp(dto.userNameLike, 'i') as any,
    };

    if (dto.visibility) {
      query.visibility = dto.visibility as any;
    }

    const fakeCtx: CrudContext = {
      ...ctx,
      query,
      data: null,
      origin: 'crud',
      method: 'GET',
    };

    await this.crudAuthorization.authorize(fakeCtx, this.security);

    const opParams = ctx.queryOptions ? {options: ctx.queryOptions} : undefined; 
    return this.$find(query, fakeCtx, opParams);
}

Note

The call to service.crudAuthorization.authorize allows us to use the CRUD security for read operations. Anything forbidden for $find will be forbidden in our search cmd as well.

profile.security.ts
guest: {
    async defineCRUDAbility(can, cannot, ctx) {
        can('read', profile, { visibility: 'public'});
    },
},

Usage

Since search returns the result of $find, it behaves as a limited command.

In our front-end, we call it with CrudClient->cmdL to benefit from the limit and offset options.

const searchDto: SearchDto = {
    userNameLike: 'doe',
    visibility: 'public',
};
const options: ICrudOptions = {
    limit: 5,
}
const { data, total, limit } = await profileClient.cmdL(
    'search',
    searchDto,
    options
);
// results : [{ userName: "Michael Doe", ... }, { userName: "Jon Doe", ...}]

Note

If no limit is specified, the client will perform multiple requests until total profiles have been fetched. The maximum limit that can be used is set in the CmdSecurity.

Note

Since search is a read-only command, you can enable allowGetMethod in the CmdSecurity. This allows for searching using a simple url: /crud/s/profile/cmd/search?query={"userNameLike":"doe"}"

Comments