Skip to content

Commit

Permalink
feat(api-service/search): validate with valibot and drop yup
Browse files Browse the repository at this point in the history
  • Loading branch information
guesant committed Nov 2, 2024
1 parent e0bba9f commit 2fb3034
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 113 deletions.
9 changes: 4 additions & 5 deletions .github/actions/prepare/install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,11 @@ runs:
nx-${{ github.ref_name }}-
nx-
- name: "node.js: enable corepack"
run: corepack enable
- name: Install pnpm
uses: pnpm/action-setup@v4
if: ${{ inputs.install-node == 'true' }}
shell: bash
env:
HUSKY: "0"
with:
run_install: false

- name: "node.js: use ${{ inputs.node-version }}"
if: ${{ inputs.install-node == 'true' }}
Expand Down
7 changes: 2 additions & 5 deletions api-service/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
"name": "@ladesa-ro/api.service",
"version": "1.0.0-next.29",
"private": true,
"files": [
"**/*"
],
"files": ["**/*"],
"scripts": {
"build": "nx build",
"generate:openapi": "nx generate:openapi",
Expand Down Expand Up @@ -72,8 +70,7 @@
"tsx": "^4.19.2",
"typeorm": "^0.3.20",
"uuid": "^11.0.2",
"valibot": "^0.42.1",
"yup": "^1.4.0"
"valibot": "^0.42.1"
},
"devDependencies": {
"@nestjs/cli": "^10.4.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export class TurmaService {
"ambientePadraoAula.capacidade": [FilterOperator.EQ, FilterOperator.GT, FilterOperator.GTE, FilterOperator.LT, FilterOperator.LTE],
"ambientePadraoAula.tipo": [FilterOperator.EQ],
//
"curso.id": [FilterOperator.EQ],
"curso.nome": [FilterOperator.EQ],
"curso.nomeAbreviado": [FilterOperator.EQ],
"curso.campus.id": [FilterOperator.EQ],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { inspect } from "util";
import * as LadesaTypings from "@ladesa-ro/especificacao";
import { PaginateQuery } from "nestjs-paginate";
import * as valibot from "valibot";

export type GenericListInputView = {
queries: LadesaTypings.GenericSearchInputView;
};

const undefinedOrType = (schema: valibot.BaseSchema<any, any, valibot.BaseIssue<any>>) =>
valibot.pipe(
valibot.union([schema, valibot.null(), valibot.undefined()]),
valibot.transform((value) => value ?? undefined),
valibot.optional(schema),
);

const paginateQueryValidation = valibot.pipe(
valibot.record(valibot.string(), valibot.unknown()),

valibot.transform((input) => {
if (input) {
const allEntries = Object.entries(input);

const otherEntries = allEntries.filter((entry) => !entry[0].startsWith("filter."));

const filterEntries = allEntries.filter((entry) => entry[0].startsWith("filter."));

return {
...Object.fromEntries(otherEntries),

filter: {
...filterEntries.reduce((acc, [key, value]) => {
const propertyPath = key.replace("filter.", "");

return {
...acc,
[propertyPath]: Array.isArray(value) ? value : [value],
};
}, {}),
},
};
}

return input;
}),

valibot.object({
page: undefinedOrType(valibot.number()),
limit: undefinedOrType(valibot.number()),

sortBy: valibot.optional(valibot.array(valibot.tuple([valibot.string(), valibot.string()]))),

searchBy: valibot.optional(valibot.array(valibot.string())),

search: undefinedOrType(valibot.string()),

filter: valibot.record(valibot.string(), valibot.array(valibot.string())),

select: valibot.optional(valibot.array(valibot.string())),

path: valibot.optional(valibot.string()),
}),
);

export class PaginateQueryAdapter {
static FromGenericListInputView(path: string, genericListInputView: GenericListInputView | null): PaginateQuery {
const inputQueries = genericListInputView?.queries;

const parseResult = valibot.parse(paginateQueryValidation, inputQueries);

const parsedQueries = parseResult.output;

const paginateQuery: PaginateQuery = {
path,
...parsedQueries,
};

return paginateQuery;
}
}
Original file line number Diff line number Diff line change
@@ -1,93 +1,26 @@
import { GenericListInputView, PaginateQueryAdapter } from "@/business-logic/standards/ladesa-spec/search/adapter";
import * as LadesaTypings from "@ladesa-ro/especificacao";
import { castArray } from "lodash";
import { PaginateConfig, PaginateQuery, Paginated, paginate } from "nestjs-paginate";
import { PaginateConfig, Paginated, paginate } from "nestjs-paginate";
import { ObjectLiteral, SelectQueryBuilder } from "typeorm";
import * as yup from "yup";

export type OperationListInputQueries = {
page?: string | number | null;
search?: string | undefined | null;
sortBy?: string | string[];
};

export type OperationListInput = {
queries: OperationListInputQueries;
};

const NestPaginateValidator = yup
.object({
page: yup.number().integer().positive().default(1),
limit: yup.number().integer().positive().default(100).min(1).max(100),

search: yup.string().default(""),

sortBy: yup
.array()
.of(
yup
.array()
.of(yup.string())
.transform((value) => {
if (typeof value === "string") {
return value.split(":");
}

return value;
}),
)
.transform((value) => {
if (!Array.isArray(value)) {
return [value];
}

return value;
})
.default([]) as any,
filter: yup.mixed({}),
})
.transform((value) => {
if (value) {
const allEntries = Object.entries(value);

const otherEntries = allEntries.filter((entry) => !entry[0].startsWith("filter."));
const filterEntries = allEntries.filter((entry) => entry[0].startsWith("filter."));

return {
...Object.fromEntries(otherEntries),

filter: filterEntries.reduce((acc, [key, value]) => {
return {
...acc,
[key.replace("filter.", "")]: Array.isArray(value) ? value : [value],
};
}, {}),
};
}
});

export const ConvertOperationListInputParamToNestPaginate = (path: string, input?: OperationListInputQueries): PaginateQuery => {
return {
path,
...NestPaginateValidator.cast(input),
};
};

export const LadesaSearch = async <T extends ObjectLiteral>(path: string, dto: OperationListInput | null, qb: SelectQueryBuilder<T>, config: PaginateConfig<T>) => {
const paginateQuery = ConvertOperationListInputParamToNestPaginate(path, dto?.queries);
return paginate(paginateQuery, qb.clone(), config);
};

export type LadesaPaginatedResult<T> = {
data: T[];
links: LadesaTypings.PaginationResultLinks;
meta: LadesaTypings.PaginationResultMeta;
};

export const LadesaSearch = async <T extends ObjectLiteral>(path: string, dto: GenericListInputView | null, qb: SelectQueryBuilder<T>, config: PaginateConfig<T>) => {
const paginateQuery = PaginateQueryAdapter.FromGenericListInputView(path, dto);
return paginate(paginateQuery, qb.clone(), config);
};

export const LadesaPaginatedResultDto = <T>(paginated: Paginated<T>): LadesaPaginatedResult<T> => {
return {
...paginated,
meta: {
...paginated.meta,

sortBy: (paginated.meta.sortBy ?? [])?.map(([key, value]) => ({
mode: value,
property: key,
Expand All @@ -100,6 +33,7 @@ export const LadesaPaginatedResultDto = <T>(paginated: Paginated<T>): LadesaPagi
}))
: [],
},

links: {
last: paginated.links.last ?? null,
next: paginated.links.next ?? null,
Expand Down
28 changes: 0 additions & 28 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 2fb3034

Please sign in to comment.