https://loopback.io/doc/en/lb4/HasMany-relation.html
I followed this steps and then tried to get data with include but I get 500.
500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
What I want is to get games object with its related ranks.
Rank Model
import { Entity, model, property, belongsTo } from '#loopback/repository';
import { Game, GameWithRelations } from './game.model';
#model({ settings: { strict: 'filter' } })
export class Rank extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#belongsTo(() => Game)
gameId: string;
constructor(data?: Partial<Rank>) {
super(data);
}
}
export interface RankRelations {
game?: GameWithRelations;
}
export type RankWithRelations = Rank & RankRelations;
Game Model
import { Entity, model, property, embedsMany, hasMany } from '#loopback/repository';
import { Rank, RankWithRelations } from './rank.model';
import { HasMany } from 'loopback-datasource-juggler';
#model({ settings: { strict: 'filter' } })
export class Game extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#hasMany<Rank>(() => Rank, { keyTo: 'gameId' })
ranks?: Rank[];
constructor(data?: Partial<Game>) {
super(data);
}
}
export interface GameRelations {
}
export type GameWithRelations = Game & GameRelations;
Game Controller
// in this method
// 500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
#get('/games/{id}')
async findById(#param.path.string('id') id: string): Promise<Game> {
return await this.gameRepository.findById(id, { include: [{ relation: 'ranks' }] });
}
Please run your application with DEBUG=loopback:repository:relation-helpers, that way you will get a debug message explaining why filter.include entry was rejected.
You can find the code building the error message here:
https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/packages/repository/src/relations/relation.helpers.ts#L96-L100
The most likely cause: your GameRepository does not have any InclusionResolver registered for ranks relation.
Please refer to our todo-list example to see how to register inclusion resolver. Cross-posting from https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/examples/todo-list/src/repositories/todo-list.repository.ts#L41-L46:
this.todos = this.createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);
this.registerInclusionResolver('todos', this.todos.inclusionResolver);
Related
I have a typeorm entity that uses single table inheritance:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
}
and some child entities:
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
#ChildEntity()
#ObjectType()
class SoftwareOrganisation {
}
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
I'm wondering how I can get the type and the child properties from the parent organisation so that I can do something like:
const organisation = await OrganisationEntity.findOne()
if(organisation.type === "medicalOrganisation"){
...
}
But it seems I'm not allowed to access the type property through the parent. Does anyone know how this can be done?
I'd prefer not to use instanceof because it requires the child entities and is causing circular dependencies.
You've got two options. Either use .createQueryBuilder and get the item using getRawOne and the returned object would contain a field named OrganisationEntity_type which can be used to do the checks. It's value would either be 'MedicalOrganisation', or 'SoftwareOrganisation' and so on.
const orgRepository: Repository<OrganisationEntity> = getRepository(OrganisationEntity);
let organisation = await orgRepository.createQueryBuilder().getRawOne();
// organisation = {
// OrganisationEntity_name: 'name',
// OrganisationEntity_users: [
// {},
// {}
// ],
// OrganisationEntity_type: 'MedicalOrganisation'
// }
Or, you could add the field type in the OrganisationEntity itself like this:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
#Column
type: string;
}
and do a straightforward:
let organisation = await OrganisationEntity.findOne();
// organisation =
// MedicalOrganisation {
// name: 'name',
// users: [{}, {}],
// type: 'MedicalOrganisation'
// }
and you get the type field in the object itself.
I am currently developing application using nestjs with fastify adapter
But something weird on object construction.
Following all the related classes, and methods:
Controller endpoint handler
#Get()
#ApiOperation({
description: "Get all user admin",
})
async findAll(
#Query() filter: GetListAdminReqFilter,
#Query() pagination: PaginatedReqDto
): Promise<RestRespDto<GetListAdminRespDto[]>> {
return new RestRespDto({
data: await this.adminService.findAll(
new GetListAdminReqDto(filter, pagination)
),
});
}
The request dto
export class GetListAdminReqDto extends PaginatedReqDto {
constructor(filter: GetListAdminReqFilter, pagination: PaginatedReqDto) {
super();
this.filter = filter;
this.pagination = pagination.pagination;
this.page = pagination.page;
}
filter?: GetListAdminReqFilter;
}
The pagination req dto
export class PaginatedReqDto {
#ApiPropertyOptional({
default: 10,
description: "Number of items to retrieve",
})
pagination?: number;
#ApiPropertyOptional({
description: "Page number, e.g:1 ",
default: 1,
})
page?: number;
}
The filter
export class GetListAdminReqFilter {
#ApiPropertyOptional()
#IsOptional()
name?: string;
#ApiPropertyOptional()
#IsOptional()
email?: string;
#ApiPropertyOptional()
#IsOptional()
divisi?: string;
#ApiPropertyOptional({ enum: AdminStatusEnum})
#IsOptional()
status?: AdminStatusEnum;
}
The result of GetListAdminReqDto object is following:
{
filter: [Object: null prototype] {
pagination: '10',
page: '1',
name: 'asdfasdf',
email: 'asdfasdf',
divisi: 'asdfasdf'
},
pagination: '10',
page: '1'
}
Why pagination and page is property also included in filter?, i dont understand what happened, any help will be appreciated
My guess is that the #Query() filter: GetListAdminReqFilter is receiving the page and pagination properties because your ValidationPipe is allowing properties outside of the DTO. You could alter the global ValidationPipe.whitelist property:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // this prevents properties outside the DTO to be ignored
}),
);
I've got a Mongoose on TypeScript, and it worked without interfaces and types defining, but when I decided to define types, the madness started. I've watched tons of manuals and topics and found nobody with a similar problem. I tried to define a model strictly by the manual, but I'm getting an error. Here's my model file:
import mongoose from 'mongoose';
import { nanoid } from 'nanoid';
const GeoSchema = new mongoose.Schema({
type: {
type: String,
default: 'Point',
enum: ['Point'],
},
coordinates: {
type: [Number, Number],
index: '2dsphere',
},
});
interface postInterface extends mongoose.Document {
shortId: string;
createdBy: string;
title: string;
description: string;
tags: string[];
location: {};
images: string[];
contacts: {
email: Boolean;
wa: Boolean;
phone: Boolean;
};
type: 'available' | 'wanted';
is_moderated: Boolean;
}
export const PostSchema = new mongoose.Schema(
{
shortId: {
type: String,
index: true,
default: () => nanoid(),
},
createdBy: {
type: String,
index: true,
},
title: String,
description: String,
tags: [String],
location: GeoSchema,
images: [String],
contacts: {
email: Boolean,
wa: Boolean,
phone: Boolean,
},
type: { type: String, enum: ['available', 'wanted'] },
is_moderated: Boolean,
},
{ timestamps: true }
);
export const Post = mongoose.model<postInterface>('Post', PostSchema);
When I import it into another file, on the line
return Post.create(post)
the linter gives me the error:
Type 'postInterface & { _id: any; }' is missing the following properties from type 'Schema<any, Model<any, any, any, any>, {}, {}>': add, childSchemas, clearIndexes, clone, and 37 more.ts(2740)
I googled examples of typescript model defining, and it looks like something that works for others doesn't work for me.
What am I doing wrong?
Update
Man, it was so obvious! The file where I imported the model looks like this:
import { Post, PostSchema } from '../models/post.model';
export class PostService {
async createPost(post: any): Promise<typeof PostSchema | null> {
return Post.create(post);
}
async getPostById(shortId: string): Promise<typeof PostSchema | null> {
return Post.findOne({ shortId });
}
}
So the thing was in Promise<typeof PostSchema | null>. When I changed the code to this, the error was gone:
import { Post, postInterface } from '../models/post.model';
export class PostService {
async createPost(post: any): Promise<postInterface | null> {
return Post.create(post);
}
async getPostById(shortId: string): Promise<postInterface | null> {
return Post.findOne({ shortId });
}
}
Thanks to #lpizzinidev for pushing me in the right direction!
Man, it was so obvious! The file where I imported the model looks like this:
import { Post, PostSchema } from '../models/post.model';
export class PostService {
async createPost(post: any): Promise<typeof PostSchema | null> {
return Post.create(post);
}
async getPostById(shortId: string): Promise<typeof PostSchema | null> {
return Post.findOne({ shortId });
}
}
So the thing was in Promise<typeof PostSchema | null>. When I changed the code to this, the error was gone:
import { Post, postInterface } from '../models/post.model';
export class PostService {
async createPost(post: any): Promise<postInterface | null> {
return Post.create(post);
}
async getPostById(shortId: string): Promise<postInterface | null> {
return Post.findOne({ shortId });
}
}
Thanks to #lpizzinidev for pushing me in the right direction!
I've been trying to recover data querying with an include, my first model is "Block" and this has a "hasMany" relation to the destiny model called "Site". Well, usually this works as intended but this time it has a catch, the data is stored in mongodb with ObjectID(), so if I try to query it on robo3t or whereever, I have to put the ObjectId and inside the ID itself of the document.
I found on the loopback 4 documentation that if I'm saving or relating data which is store with objectId, I have to set the property dataType:ObjectId, but still dosnt work.
My Block model:
import {Entity, model, property, hasMany} from '#loopback/repository';
import {Site} from './site.model';
#model({
settings: {
strict: false,
mongodb: {
collection: 'bloques',
},
},
})
export class Block extends Entity {
#property({
type: 'string',
id: true,
defaultFn: 'uuidv4',
mongodb: {dataType: 'ObjectId'},
})
id: string;
#property({
type: 'date',
required: true,
})
dateBlock: string;
#property({
type: 'object',
required: true,
})
statuses: object;
#hasMany(() => Site)
minedIds: Site[];
/* #hasMany(() => Site)
minedIds: Site[];*/
[prop: string]: any;
constructor(data?: Partial<Block>) {
super(data);
}
}
export interface BlockRelations {
// describe navigational properties here
}
export type BlockWithRelations = Block & BlockRelations;
My Site Model:
import {Entity, hasMany, model, property} from '#loopback/repository';
import {CachimbaModel} from './cachimba-model.model';
#model({
settings: {
// model definition goes in here
mongodb: {collection: 'minadas'},
},
})
export class Site extends Entity {
#property({
type: 'string',
id: true,
generated: true,
mongodb: {dataType: 'ObjectId'},
})
id?: string;
#property({
type: 'date',
required: true,
})
lastUpdate: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
logo: string;
#hasMany(() => CachimbaModel)
data: CachimbaModel[];
#property({
type: 'string',
dataType: 'ObjectId',
})
blockId?: string;
constructor(data?: Partial<Site>) {
super(data);
}
}
export interface SiteRelations {
// describe navigational properties here
}
export type SiteWithRelations = Site & SiteRelations;
And their respective repos are as the following:
block-repo
import {Getter, inject} from '#loopback/core';
import {DefaultCrudRepository, repository, HasManyRepositoryFactory} from '#loopback/repository';
import {DbDataSource} from '../datasources';
import {Block, BlockRelations, Site} from '../models';
import {SiteRepository} from './site.repository';
export class BlockRepository extends DefaultCrudRepository<
Block,
typeof Block.prototype.id,
BlockRelations
> {
public readonly minedIds: HasManyRepositoryFactory<Site, typeof Block.prototype.id>;
constructor(
#inject('datasources.DbDataSource') dataSource: DbDataSource,
#repository.getter('SiteRepository')
protected siteRepositoryGetter: Getter<SiteRepository>,
) {
super(Block, dataSource);
this.minedIds = this.createHasManyRepositoryFactoryFor('minedIds', siteRepositoryGetter,);
this.registerInclusionResolver('minedIds', this.minedIds.inclusionResolver);
}
}
site-repo
import {Getter, inject} from '#loopback/core';
import {
DefaultCrudRepository,
HasManyRepositoryFactory,
repository,
} from '#loopback/repository';
import {DbDataSource} from '../datasources';
import {CachimbaModel, Site, SiteRelations} from '../models';
import {CachimbaModelRepository} from './cachimba-model.repository';
export class SiteRepository extends DefaultCrudRepository<
Site,
typeof Site.prototype.id,
SiteRelations
> {
public readonly data: HasManyRepositoryFactory<
CachimbaModel,
typeof Site.prototype.id
>;
constructor(
#inject('datasources.DbDataSource') dataSource: DbDataSource,
#repository.getter('CachimbaModelRepository')
protected cachimbaModelRepositoryGetter: Getter<CachimbaModelRepository>,
) {
super(Site, dataSource);
this.data = this.createHasManyRepositoryFactoryFor(
'data',
cachimbaModelRepositoryGetter,
);
this.registerInclusionResolver('data', this.data.inclusionResolver);
}
}
I try to query and obtain the nested data from the block controller using an include as the following:
And my database has the following values:
I will apreaciate your help for real! Thanks in advance!
My bad, I didnt have to set the ids on minedIds property, I had to set it on the destinatary model property opposite to the hasMany
As Documented on TypeOrm FrameWork Repository.save should save/insert new values and ignore/update the existing once,
But now I'm facing a problem that it's thrown a duplication error on existing value and stoping the whole inserting! ( I have a unique column called key )
My entity:
import { Entity, Column, PrimaryGeneratedColumn, ManyToOne, JoinColumn, PrimaryColumn } from 'typeorm';
import { Source } from '../sources/source.entity';
#Entity({ name: 'articles' })
export class Article {
#PrimaryGeneratedColumn()
id: number;
#Column()
title: string;
#Column({
nullable: true
})
image: string | null;
#Column({
type: "text",
})
url: string;
#Column({
type: "varchar",
unique: true
})
key: string;
#Column({
type: 'datetime',
nullable: true
})
date: Date | null;
#ManyToOne(type => Source, source => source.articles, {eager: true})
#JoinColumn({name: 'source'})
source: Source;
#Column({
type: `text`,
nullable: true
})
description: string | null
}
My Service:
constructor(
#InjectRepository(Article) private readonly articleRepository: Repository<Article>,
private readonly articlesScraper: BlogScraperService
) {
}
async clonningFromScraper() {
let articles = await this.articlesScraper.articles('1');
articles = articles.map(article => ({ ...article, key: decodeURIComponent(article.url).substring(0, 255) }));
return this.articleRepository
.save(articles);
}
I have ended up solving this by RAW SQL query using the following
return this.articleRepository.query(
"INSERT IGNORE INTO articles ( title, date, url, image, source, description, _key ) VALUES ?", [querableArticles]);