complex query in typeorm - database

hello every body i have entity that contain special property used for translation, when i try to get this special property usin typeorm i have this error: (node:3712) UnhandledPromiseRejectionWarning: QueryFailedError: syntax error at or near "[">>>
the entity is:
import {
Entity,
PrimaryGeneratedColumn,
Column,
BaseEntity,
OneToOne,
JoinColumn,
} from "typeorm";
import {TextData} from "./TextData"
#Entity()
export class ContactUsDataNature extends BaseEntity {
#PrimaryGeneratedColumn()
id:number
#OneToOne((type) => TextData, (text) => text.id, { cascade: true })
#JoinColumn()
dataNature:TextData
}
the text data is:
import { Entity, Column, PrimaryGeneratedColumn, BaseEntity } from "typeorm";
#Entity()
export class TextData extends BaseEntity{
#PrimaryGeneratedColumn()
id: number;
#Column({ type: "text", nullable: true })
ar: string;
#Column({ type: "text", nullable: true })
en: string;
}
i usually reach it like
const contact=new ContactUSDataNature()
console.log(contact.dataNature[en])
i tried the way to search but it doesn't work :
export const getContactDataNaturebyData = async (dataNature: string,language="en") =>{
const container=await ContactUsDataNature.find({
where: (qb) => {
if (true) {
qb.andWhere("ContactUsDataNature__dataNature.:lan = :parentId", {
lan:"en",
parentId: 1,
});
}
},
order: { id: "DESC" },
skip: 10,
take: 0,
});
console.log(container)
return container
}
I need to get search in database for ContactUsDataNature if i get the final string value

You cannot parameterize the column names in a query.
You can build the where by concatenating the column name:
qb.andWhere("ContactUsDataNature__dataNature." + lan + " = :parentId", { parentId: 1 });
When you test this, I recommend that you turn on TypeOrm full logging so you can see the actual generated SQL and you be able to quickly solve any problems. See TypeOrm logging.

Related

Typegoose + mongoose: how to push tuple to array?

I'm working on the following code:
import { prop, modelOptions, DocumentType, getModelForClass } from "#typegoose/typegoose";
import dbConnect from "src/lib/dbConnect";
import mongoose from 'mongoose';
#modelOptions({ schemaOptions: { timestamps: true } })
class Kitten {
#prop()
public name?: string;
#prop({ type: String, required: true, default: [] })
public events!: mongoose.Types.Array<[string, Date]>;
// also doesn't work:
// public events!: [string, Date][];
// instance method:
public async addEvent(this: DocumentType<Kitten>, _eventName: string) {
const tuple : [string, Date] = [_eventName, new Date()];
this.events.push(tuple);
await dbConnect();
await this.save();
}
}
export const KittenModel = getModelForClass(Kitten);
and when I call addEvent I get the following error when executing this.events.push(tuple); :
error - CastError: Cast to string failed for value "[
'0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001',
2023-01-11T05:02:21.462Z
]" (type Array)
It seems that the method push is trying to convert the tuple into a string for some reason...
My environment is:
"#typegoose/typegoose": "^10.0.0",
"mongoose": "^6.8.3",
nodejs --version :v16.18.1
Mongoose (and typegoose by extension) dont support tuple arrays unless the field is Mixed (which is not recommended to be used).
In your case a Map would probably what you search for, if the event names are unique, if they are not unique you may wanna use a nested class to keep the fields explicitly, like:
class EventDate {
#prop()
public eventName: string;
#prop()
public date: Date;
}
class Kitten {
#prop({ type: EventDate, required: true, default: [] })
public events!: mongoose.Types.Array<EventDate>;
public async addEvent(this: DocumentType<Kitten>, _eventName: string) {
this.events.push({ eventName: _eventName, date: new Date() });
await dbConnect();
await this.save();
}
}
The issue is that the decorator #prop was not being well defined.
It should be:
#prop({ type: mongoose.Types.Array<[string, Date]>, required: true, default: [] })
public events!: mongoose.Types.Array<[string, Date]>;
The complete code is:
import { prop, modelOptions, DocumentType, getModelForClass } from "#typegoose/typegoose";
import dbConnect from "src/lib/dbConnect";
import mongoose from 'mongoose';
#modelOptions({ schemaOptions: { timestamps: true } })
class Kitten {
#prop()
public name?: string;
#prop({ type: mongoose.Types.Array<[string, Date]>, required: true, default: [] })
public events!: mongoose.Types.Array<[string, Date]>;
// instance method:
public async addEvent(this: DocumentType<Kitten>, _eventName: string) {
const tuple : [string, Date] = [_eventName, new Date()];
this.events.push(tuple);
await dbConnect();
await this.save();
}
}
export const KittenModel = getModelForClass(Kitten);

How to implement server-side columns filtering with different filter operators in TanStack Table (React-Table V8)

I am trying to implement server-side column filtering in TanStack Table (React-Table V8). The problem is that I don't know how to convert the ColumnFiltersState object to the right format for the backend. My backend uses nestjs-paginate which uses the following operators: $eq, $not, $null, $in, $gt, $gte, $lt, $lte, $btw, $ilike for filtering based on the query. The ColumnFiltersState contains a value field, which is of type unknown, and the value it contains will depend on the filter component being used, I plan to use select, date inputs, range inputs for filtering. Also, some fields can be filtered as 'contains', 'equals' etc.
Additionally, another problem is that I need to support both client-side and server-side column filtering depending on the manualFiltering prop passed to my custom table component.
Here is a piece of code from my UsersList component which use my custom table:
...
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const { data, isFetching, isLoading, isSuccess, isError } = useGetUsersQuery({
search: globalFilter,
page: pageIndex + 1,
limit: pageSize,
sortBy: sorting.map((s) => `${s.id}:${s.desc ? 'DESC' : 'ASC'}`).join(','),
columnFilters: columnFilters.map((columnFilter) => { // Here is the problem
...
return {
column: columnFilter.id,
filter: {
operator: ...,
value: ...,
},
};
}),
});
...
So then I can send it to the server in the needed format:
// user.api.ts
...
query: ({ search, page, limit, sortBy, columnFilters }) => {
const params: any = {
page,
limit,
search,
sortBy,
};
// example query URL with filters:
// http://localhost:3000/cats?filter.age=$gte:3
if (columnFilters) {
columnFilters.forEach(({ column, filter: { operator, value } }) => {
params[`filter.${column}`] = `${operator}:${value}`;
});
}
return {
url: 'users/',
params,
};
},
...
Here are the nestjs-paginate types:
// Available filter operators:
export enum FilterOperator {
EQ = '$eq',
GT = '$gt',
GTE = '$gte',
IN = '$in',
NULL = '$null',
LT = '$lt',
LTE = '$lte',
BTW = '$btw',
NOT = '$not',
ILIKE = '$ilike',
}
// Return data type from the backend
export interface Paginated<T> {
data: T[];
meta: {
itemsPerPage: number;
totalItems: number;
currentPage: number;
totalPages: number;
sortBy: SortBy<T>;
searchBy: Column<T>[];
search: string;
filter?: {
[column: string]: string | string[];
};
};
links: {
first?: string;
previous?: string;
current: string;
next?: string;
last?: string;
};
}
A possible solution is to add a custom prop to ColumnMeta by which it will be possible to determine which filtering operator to use, but then I don’t know how to do it correctly or maybe I could determine it using the built-in solutions. I would appreciate any help and ideas πŸ™‚.

How to create field resolver on RedwoodJS

RedwoodJS automatically maps GraphQL queries resolvers to api/src/services. How do I create a field resolver for a given GraphQL type?
Suppose I have this schema:
type Person {
name: string!
birthDate: DateTime!
age: Int!
}
But only name and birthDate are stored in the database.
Using graphql-tools I would write my resolvers like this:
const resolvers = {
Query: { ... },
Mutation: { ... },
Person: {
age(person) {
return new Date().getFullYear() - person.birthDate.getFullYear();
},
},
};
PS: I know the age formula is wrong.
PS2: I'm using age here for the sake of simplicity, imagine this is expensive to compute or get from database.
It's almost identical to the way you do it with graphql-tools.
You export an object with the same name as your type in your service:
// services/person.js
export const Person = {
age: (_args, { root }) {
return new Date().getFullYear() - root.birthDate.getFullYear();
},
}
As an aside, you could also export a resolvers in the person.sdl.js file (But services take precendence):
// graphql/person.sdl.js
export const schema = gql`/* ... */`
export const resolvers = {
Query: {},
Mutation: {},
Person: {},
}
Edit: I misunderstood the question, this answer just covers creating query + mutation resolvers, not a resolver for a computed field.
To create a field resolver, you'll need to decide whether you're creating a resolver for a query, or a handler for a mutation.
We can use the following schema as an example:
export const schema = gql`
type Person {
id: String!
name: String!
age: Int!
}
type PersonInput {
name: String
age: Int
}
type Mutation {
createPerson(input: PersonInput!): Person
}
type Query {
people: [Person]
person(id: String!): Person
}
`
If the above schema is stored in a file called persons.sdl.js, in the api/src/graphql directory, you can implement the queries and mutations in a file called persons.js in the api/src/services/persons directory.
// implements Mutation.createPerson(...)
export const createPerson({ input }) => {
return db.person.create({
data: input
})
}
// implements Query.people
export const people = () => {
return db.person.findMany()
}
// implements Query.person(...)
export const person = ({ id }) => {
return db.person.findOne({
where: { id }
})
}

Class-validator - validate array of objects

I am using class-validator package with NestJS and I am looking to validate an array of objects that need to have exactly 2 objects with the same layout:
So far I have:
import { IsString, IsNumber } from 'class-validator';
export class AuthParam {
#IsNumber()
id: number;
#IsString()
type: string;
#IsString()
value: string;
}
and
import { IsArray, ValidateNested } from 'class-validator';
import { AuthParam } from './authParam.model';
export class SignIn {
#IsArray()
#ValidateNested({ each: true })
authParameters: AuthParam[];
}
per #kamilg response (I am able to enforce exacly 2 elements):
import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';
export class SignInModel {
#IsArray()
#ValidateNested({ each: true })
#ArrayMinSize(2)
#ArrayMaxSize(2)
authParameters: AuthParam[];
}
I still can pass an empty array or an array with some other objects not related to AuthParam.
How I should modify it get validation?
Also how I can enforce mandatory 2 elements in the array? MinLength(2) seems to be regarding string... (resolved)
Add #Type(() => AuthParam) to your array and it should be working. Type decorator is required for nested objects(arrays). Your code becomes
import { IsArray, ValidateNested, ArrayMinSize, ArrayMaxSize } from 'class-validator';
import { AuthParam } from './authParam.model';
import { Type } from 'class-transformer';
export class SignInModel {
#IsArray()
#ValidateNested({ each: true })
#ArrayMinSize(2)
#ArrayMaxSize(2)
#Type(() => AuthParam)
authParameters: AuthParam[];
}
Be careful if you are using any exception filter to modify the error reponse. Make sure you understand the structure of the class-validator errors.
I Know I Am Late But Facing Some Issue With Type, Then Try Another Way To Implement This:
export class AuthParam {
#IsNumber()
id: number;
#IsString()
type: string;
#IsString()
value: string;
}
Validation function
#ValidatorConstraint()
export class IsAuthArray implements ValidatorConstraintInterface {
public async validate(authData: AuthParam[], args: ValidationArguments) {
return Array.isArray(authData) && authData.reduce((a, b) => a && (typeof b.id === "number") && typeof b.type === "string" && typeof b.field === "string", true);
}
}
export class SignInModel {
#IsNotEmpty()
#IsArray()
#ArrayMinSize(2)
#ArrayMaxSize(2)
#Validate(IsAuthArray, {
message: "Enter valid value .",
})
authParameters: AuthParam[];
}
Maybe It Will Help Someone πŸ˜ƒ
You can use the following:
validator.arrayNotEmpty(array); // Checks if given array is not empty.
validator.arrayMinSize(array, min); // Checks if array's length is at least `min` number.
(https://github.com/typestack/class-validator#manual-validation)
You may want to consider writing custom validator which would better reflect the business requirement you have.
const param1: AuthParam = Object.assign(new AuthParam(), {
id: 1,
type: 'grant',
value: 'password'
})
const param2: AuthParam = Object.assign(new AuthParam(), {
id: 1,
type: 4,
value: 'password'
})
const signInTest = new SignInModel()
signInTest.authParameters = [param1, param2]
validate(signInTest).then(e => {
console.log(e[0].children[0].children[0])
})
This works correctly, this is:
ValidationError {
target: AuthParam { id: 1, type: 4, value: 'password' },
value: 4,
property: 'type',
children: [],
constraints: { isString: 'type must be a string' } }
so I may only assume that object which is being validated, is not an instance of AuthParam
const param2: AuthParam = {
id: 1,
type: 4,
value: 'password'
} as any
as expected, there aren't any decorators on this object (which may be true for Nest.js controllers and nested objects from body/req) - so validation is ignored.
Please check this (tl;dr - #Type decorator form class-transformer)

My query is failing in relay and I don't know why?

I have this simple query which works fine in my Graphql but I cannot pass data using relay to components and I don't know why :(
{
todolist { // todolist returns array of objects of todo
id
text
done
}
}
this is my code in an attempt to pass data in components using relay:
class TodoList extends React.Component {
render() {
return <ul>
{this.props.todos.todolist.map((todo) => {
<Todo todo={todo} />
})}
</ul>;
}
}
export default Relay.createContainer(TodoList, {
fragments: {
todos: () => Relay.QL`
fragment on Query {
todolist {
id
text
done
}
}
`,
},
});
And lastly my schema
const Todo = new GraphQLObjectType({
name: 'Todo',
description: 'This contains list of todos which belong to its\' (Persons)users',
fields: () => {
return {
id: {
type: GraphQLInt,
resolve: (todo) => {
return todo.id;
}
},
text: {
type: GraphQLString,
resolve: (todo) => {
return todo.text;
}
},
done: {
type: GraphQLBoolean,
resolve: (todo) => {
return todo.done;
}
},
}
}
});
const Query = new GraphQLObjectType({
name: 'Query',
description: 'This is the root query',
fields: () => {
return {
todolist: {
type: new GraphQLList(Todo),
resolve: (root, args) => {
return Conn.models.todo.findAll({ where: args})
}
}
}
}
});
This code looks simple and I cannot see why this won't work and I have this error Uncaught TypeError: Cannot read property 'todolist' of undefined, but I configure todolist and I can query in my graphql, you can see the structure of the query is same, I don't know why this is not working?
todolist should be a connection type on Query. Also, your ids should be Relay global IDs. You will not have access to your objects' raw native id fields in Relay.
import {
connectionArgs,
connectionDefinitions,
globalIdField,
} from 'graphql-relay';
// I'm renaming Todo to TodoType
const TodoType = new GraphQLObjectType({
...,
fields: {
id: uidGlobalIdField('Todo'),
...
},
});
const {
connectionType: TodoConnection,
} = connectionDefinitions({ name: 'Todo', nodeType: TodoType });
// Also renaming Query to QueryType
const QueryType = new GraphQLObjectType({
...,
fields: {
id: globalIdField('Query', $queryId), // hard-code queryId if you only have one Query concept (Facebook thinks of this top level field as being a user, so the $queryId would be the user id in their world)
todos: { // Better than todoList; generally if it's plural in Relay it's assumed to be a connection or list
type: TodoConnection,
args: connectionArgs,
},
},
});
// Now, to be able to query off of QueryType
const viewerDefaultField = {
query: { // Normally this is called `viewer`, but `query` is ok (I think)
query: QueryType,
resolve: () => ({}),
description: 'The entry point into the graph',
}
};
export { viewerDefaultField };
The above is not fully complete (you'll likely also need to setup a node interface on one or more of your types, which will require node definitions), but it should answer your basic question and get you started.
It's a huge, huge pain to learn, but once you struggle through it it starts to make sense and you'll begin to love it over RESTful calls.

Resources