Loopback4 hasMany not return the link array - has-many

I just try to use the hasMany relationship according to the loopback4 documentation .but it's not working as expected.
My Bus Model =>
export class Bus extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#hasMany(() => BusStation, {keyTo: 'busId'})
stations?: BusStation[];
constructor(data?: Partial<Bus>) {
super(data);
}
}
export interface BusRelations {
// describe navigational properties here
}
export type BusWithRelations = Bus & BusRelations ;
Bus Station Model =>
export class BusStation extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'number',
})
busId: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<BusStation>) {
super(data);
}
}
export interface BusStationRelations {
// describe navigational properties here
}
export type BusStationWithRelations = BusStation & BusStationRelations;
Bus Repository =>
export class BusRepository extends DefaultCrudRepository<
Bus,
typeof Bus.prototype.id,
BusRelations
> {
public stations: HasManyRepositoryFactory<
BusStation,
typeof Bus.prototype.id
>;
constructor(
#inject('datasources') dataSource: MyDataSource,
#repository.getter('BusStationRepository')
busStationRepositoryGetter: Getter<BusStationRepository>,
) {
super(Bus, dataSource);
this.stations = this.createHasManyRepositoryFactoryFor(
'stations',
busStationnRepositoryGetter,
);
}
}
My Expected Get Response of Bus =>
{
" id":1,
"stations":[
{
"id":1,
"busId":1,
"name":"Station 1"
}
]
}
I did the exactly same with the documentation but why I can't get the response as I expected. Please May I know what I am missing?
I saw that some solution is to create another controller to connect these two models. Is it the only way? if yes, what is the reason for the hasMany?

in repository you have to include inclusion resolver under
this.stations = this.createHasManyRepositoryFactoryFor('stations',busStationnRepositoryGetter,);
like this
this.registerInclusionResolver('stations', this.stations.inclusionResolver);
on the other hand, you can use relation generator to avoid all this stuff.

Related

How to use graphql-type-json with NestJS

i Have following response that needs to go under GraphQL Query:
{
'0xF7446f0...9a496aE94Cf9d42': { balances: [ [Object], [Object] ] },
'0xc01679F6496...95c86f9DEc63a': { balances: [ [Object], [Object] ] }
}
Using nestjs together with graphql-type-json and my code looks like this
#ObjectType()
export class BalancesResponse {
#Field({ nullable: true })
error?: string;
#Field((type) => JSON)
balances: any;
}
but when i try to execute the requests i got this error:
"message": "Cannot return null for non-nullable field BalancesResponse.balances."
Any idea how to return it, i want to return all of the key-value pairs in the object and my key to be dynamic
GraphQL does not support dictionary types. You can only query defined (i.e. known in advance) fields.
You could, however, refactor the response object to be an array:
[
{ key: '0xF7446f0...9a496aE94Cf9d42', balances: [ ... ] },
{ key: '0xc01679F6496...95c86f9DEc63a', balances: [ ... ] }
]
The corresponding type definition would be:
#ObjectType()
export class BalanceResponse {
#Field()
key: string;
#Field({ nullable: true })
error?: string;
#Field((type) => JSON)
balances: any;
}
...
#Query(() => [BalanceResponse])
async getBalance(): Promise<BalanceResponse[]>
Alternatively, you are free to lose the typing by declaring the whole response object as a GraphQL JSON. Then you can return your original response from the endpoint:
// not a GraphQL class anymore
export class BalancesResponse {
error?: string;
balances: Balance;
}
...
#Query(() => JSON)
async getBalance(): Promise<Record<string, BalancesResponse>>

How to use class-validator validate Date is older than now and unique key in an array?

Using class-validator with Nest.js. I want to validate these two cases:
Validate the input date is older than now, then give a message: Date can't before than now.
#Field(() => Date, { description: 'Due Date' })
dueDate: Date;
Validate if all of the keys are unique in an array. But this way only can check if the ID is uuid. Is it possible to check if the IDs are the same in the array? Ex: ['1234-1234-1234-1234', '1234-1234-1234-1234']
#Field(() => [String], { description: 'product IDs' })
#IsUUID('all', { each: true, message: 'Product ID is not valid.' })
productIds: string[];
I searched and couldn't find a suitable inheriting validation decorators. You can custom validation classes like this:
#ValidatorConstraint()
export class IsAfterNowConstraint implements ValidatorConstraintInterface {
validate(date: Date) {
return Date.now() < date.getTime();
}
defaultMessage(args: ValidationArguments) {
return `Date ${args.property} can not before now.`;
}
}
function IsAfterNow(validationOptions?: ValidationOptions) {
// eslint-disable-next-line #typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsAfterNowConstraint,
});
};
}
#ArrayUnique(identifier?: (o) => any): Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion.
You can compare two dates with this function
import { ValidateBy, ValidationOptions, buildMessage, ValidationArguments } from 'class-validator'
export const IsAfter = (property: string, options?: ValidationOptions): PropertyDecorator =>
ValidateBy(
{
name: 'IsAfter',
constraints: [property],
validator: {
validate: (value: Date, args: ValidationArguments): boolean => {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as Record<string, unknown>)[relatedPropertyName] as Date
return value.toISOString() > relatedValue.toISOString()
},
defaultMessage: buildMessage((each: string): string => each + '$property must be after $constraint1', options),
},
},
options,
)
in this kind of situations
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly from!: Date
#IsAfter('from')
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly to!: Date

Get ManyToOne field using TypeORM and Graph QL query

I have a problem with a TypeORM entity from my project.
The code for entity is shown below.
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { MusicProvider } from "./MusicProvider";
import { User } from "./User";
#ObjectType()
#Entity()
export class UserMusicProvider extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field(() => User)
#ManyToOne(() => User, (user) => user.id, { onDelete: 'CASCADE', cascade: true })
user: User
#PrimaryColumn()
userId: number
#Field(() => MusicProvider)
#ManyToOne(() => MusicProvider, (musicProvider) => musicProvider.id, { onDelete: 'CASCADE', cascade: true })
musicProvider: MusicProvider
#Column()
musicProviderId: number
#Field()
#Column()
musicProviderUserId: string;
}
As you can see, it has a field for MusicProvider. Into my GraphQL query I want to be able to get the MusicProvider's data, because I want to show this data into my app. I want to do this whitin this query, not using two queries.
The problem is that when I call this query:
query{
getUserMusicProviders(userId: 27){
userMusicProviders{
id
musicProviderUserId
musicProvider{
id
name
}
}
errors{
field
message
}
}
}
i receive an error saying:
Cannot return null for non-nullable field
UserMusicProvider.musicProvider.
which is true, because, as I checked, the MusicProvider returned is undefined. I don't know why this happens and would be really helpful to solve this issue.
The code for my query is this:
#Query(() => UserMusicProvidersResponse)
async getUserMusicProviders(
#Arg("userId") userId: number
): Promise<UserMusicProvidersResponse | null> {
const resp = await UserMusicProvider.find({ where: { userId } })
return {
userMusicProviders: resp
}
}
and the code for the UserMusicProvidersResponse is:
#ObjectType()
export class UserMusicProvidersResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => [UserMusicProvider], { nullable: true })
userMusicProviders?: UserMusicProvider[]
}
Thank you for your time and thank you for trying to help me. If you want to clarify something, feel free to send a comment.

TypeGraphql Field and Arg decorators using custom type

I'm trying to build a resolver using type-graphql library and found that I can't define custom argument type. Here is my code:
type Hits = { [K: string]: string | number }
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
#UseMiddleware(isAuthenticated)
async searchAssetList(#Arg('hits') hits: Hits) {
return [];
}
}
I got an error:
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript reflection system. You need to provide explicit type for argument named 'hits' of 'searchAssetList' of 'SearchResolver' class.
I also tried to define an input class:
type Hits = { [K: string]: string | number }
#ObjectType()
class SearchListInput {
#Field(() => GraphQLObjectType)
hits: Hits;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: SearchListInput) {
return []
}
}
and got another error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for argument named 'input' of 'searchAssetList' of 'SearchResolver' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Replacing #ObjectType with #InputType also doesn't help. How to define decorators like #Field, #Arg correctly?
Any help is appreciated. Thanks.
I think you are trying to have an Arg of an array whit numbers and strings in that case you should do
#InputType()
class HitsInput
{
#Field()
Hits: [number | string];
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
if this is not the case and you want an object whit dynamic fields you need to define the object, let say if hits have Name and Id_Hits,
#InputType()
class HitsInput
{
#Field()
Id_Hits: number;
#Field()
Name: string;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
and if you want to have a dynamic args base in the user request I am not quite sure that it is possible, but it is possible to do this
#InputType()
class HitsInput
{
[key: string]: any;
}
I had exactly the same issue, a couple of days ago, and I solve it using GraphQLScalarType by creating my own custom type
here how to do it
import { GraphQLScalarType} from "graphql";
export const GraphQLAny = new GraphQLScalarType({
name: 'Any',
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => ast
});
and in your class you can use your customTpe like this:
#ObjectType()
class SearchListInput {
#Field(() => GraphQLAny)
hits: typeof GraphQLAny;
}

NestJS x Graphql x Typegoose issue on sub resolver

I've got an issue with an interface type and some sub resolvers for the common part of the schema, here is my code and the issue. I might be wrong by declaring that way but I give you the goal and if you have any other design that matches the need, feel free to share it with me.
I'm using NestJS 7+, Typegoose, and nest Graphql with the CLI plugin that defines annotations auto.
I have an application where I have two kinds of users: Professional and Individual.
In the database, they are saved under a common document, fields that are in common lives in the document body, and specialized fields live in whether
individual or legalEntity key (it's an object).
#ObjectType()
class IndividualData {
#prop()
firstName: string;
#prop()
lastName: string;
}
#ObjectType()
class LegalEntity {
#prop()
tradeName: string;
}
#ObjectType({ description: "DO NOT USE, JUST FOR EXTENDING" })
export class User {
#Field(() => ObjectId)
_id: ObjectId;
#Field(() => Date)
createdAt: Readonly<Date>;
#Field(() => Date)
updatedAt: Readonly<Date>;
#Field(() => EmailScalar)
#prop({ unique: true })
email: string;
#Field(() => PasswordScalar)
#prop()
password: string;
#prop()
validationToken?: string;
#prop()
isValidated: boolean;
#prop()
isVolunteer: boolean;
#prop()
lastLogin: Date;
#prop()
photo?: string;
#prop()
phone?: string;
#prop()
city?: string;
#prop()
country?: string;
#prop()
individual?: IndividualData; // For individuals
#prop()
legalEntity?: LegalEntity; // For pro
#Field(() => [ProductDTO])
#arrayProp({ items: ObjectId })
listings: Types.Array<ObjectId>; // Sale products as user
#Field(() => VolunteersTeam) // Used in join
#prop()
volunteersTeam?: ObjectId;
#Field(() => [Supplier]) // Used in join
#arrayProp({ items: ObjectId })
suppliers?: Types.Array<ObjectId>; // Suppliers space in which user is member
}
#InterfaceType({
resolveType(item: UserDocument) {
return item.legalEntity ? "ProfessionnalUserDTO" : "IndividualUserDTO";
},
})
export class CurrentUserDTO extends OmitType(
User,
["password", "validationToken", "legalEntity", "individual"],
InterfaceType,
) {}
#ObjectType({
implements: [CurrentUserDTO],
})
export class IndividualUserDTO extends CurrentUserDTO {
firstName: string;
lastName: string;
}
#ObjectType({
implements: [CurrentUserDTO],
})
export class ProfessionnalUserDTO extends CurrentUserDTO {
legalName: string;
}
Now here is my sub-resolver that is used to get common to both subfield which is named suppliers
#Resolver(() => CurrentUserDTO)
export class CurrentUserDTOResolver {
constructor(
private readonly supplierService: SupplierService,
private readonly volunteerService: VolunteerService,
private readonly productService: ProductService,
) {}
#ResolveField(() => [Supplier])
async suppliers(#Parent() user: CurrentUserDTO) {
return user.suppliers.length > 0
? this.supplierService.find({ _id: { $in: user.suppliers } })
: [];
}
}
I'm now getting
(node:58160) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'properties' of undefined
Any idea?
Looks like du to #Resolver(() => CurrentUserDTO), CurrentUserDTO does not exists in this.objectTypes at compileExternalFieldResolverMetadata
If I try to add #ObjectType() under #InterfaceType at CurrentUserDTO
I get Error: Schema must contain uniquely named types but contains multiple types named "CurrentUserDTO". which is normal…
Therefore I've no idea…
How do define sub_resolver for the interface type
I'm dead with that…
Do you have any suggestions / ideas?
Thank you,
Andréas
As you cannot define resolvers in NestJS for interface (and in grpahql generally) you could achieve that behavior by doing what's following:
// One annotation per things that implement my CurrentUserDTO interface
#Resolver(() => IndividualUserDTO)
#Resolver(() => ProfessionnalUserDTO)
export class CurrentUserDTOResolver {
constructor(
private readonly supplierService: SupplierService,
private readonly volunteerService: VolunteerService,
private readonly productService: ProductService,
) {}
#ResolveField(() => [Supplier])
async suppliers(#Parent() user: CurrentUserDTO) {
return user.suppliers.length > 0
? this.supplierService.find({ _id: { $in: user.suppliers } })
: [];
}
}

Resources