I'm using Nestjs and Mongoose orm the problem is ValidationPipe doesn't delete invalid props from request and send given(raw) request to my service.
This is main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: { origin: '*' },
});
app.useGlobalPipes(new ValidationPipe(
{
transform: true,
whitelist: true,
}
))
await app.listen(3002);
}
bootstrap();
and this is update-category.dto
export class UpdateCategoryDto {
#IsDefined()
id:string
#IsDefined()
title: string;
#IsDefined()
url: string;
}
And finally this is category.service
async updateCategories(categories: [UpdateCategoryDto]){
for (let i = 0; i < categories.length ; i++) {
console.log("given categories",categories);
await this.categoryModel.updateOne([{ _id: categories[i].id }],categories[i]);
}
}
Here is my simple controller
#Controller('categories')
export class CategoryController {
constructor(private categoryService: CategoryService) {}
#Put()
editCategories( #Body() updateCategories: [UpdateCategoryDto]) {
return this.categoryService.updateCategories(updateCategories);
}
}
when "given categories" logs items, they have _id which frontend sent to api while I didn't whitelisted that props in my dto. why I'm receaving that prop?? I also tried `forbidNonWhitelisted' and interestingly request didn't fail :)) seems useGlobalPipes doesn't work for me
Just use ParseArrayPipe.
Update your controller.ts:
#Put()
editCategories(#Body(new ParseArrayPipe({ items: UpdateCategoryDto, whitelist: true })) updateCategories: UpdateCategoryDto[]) {
return this.categoryService.updateCategories(updateCategories);
}
Ensure to have items and whitelist set.
Related
I am trying to send a direct function call, and I set the DTO, but not work.
here is the code (Send is my DTO) in my controller:
#Post('/Send')
async SendE(body: Send) {
const mail = await this.messageProducer.SendMessage(body);
return mail;
}
I make a direct call to SendE function here:
#MessagePattern('Notification')
async readMessage(#Payload() message: any, #Ctx() context: KafkaContext) {
const messageString = JSON.stringify(context.getMessage().value);
const toJson = JSON.parse(messageString);
await this.SendE(toJson);
}
I want the "Send" DTO can validate the "toJson", but it does not work.
here is what my DTO looks like:
export class Send{
#IsString()
#ApiProperty({ required: true })
MessageID: string;
}
here is what the toJson looks like:
{
MessageID: 123
}
If I send a non-string MessageID, it can pass the DTO.
Please help
You have to enable validationPipe to enable DTO's, There are 2 approaches according to the docs of NestJS. The ValidationPipe is exported from #nestjs/common.
1. Globally in your main.ts:
// validate incoming requests
app.useGlobalPipes(
new ValidationPipe({
transform: true,
})
);
2. Per controller
#Post()
#UsePipes(new ValidationPipe({ transform: true }))
async create(#Body() createCatDto: CreateCatDto) {
this.catsService.create(createCatDto);
}
I am using graphql-request as a GraphQL client to query a headless CMS to fetch stuff, modify and return to the original request/query. headless cms is hosted separately fyi.
I have the following code :
#Query(returns => BlogPost)
async test() {
const endpoint = 'https://contentxx.com/api/content/project-dev/graphql'
const graphQLClient = new GraphQLClient(endpoint, {
headers: {
authorization: 'Bearer xxxxxxx',
},
})
const query = gql`
{
findContentContent(id: "9f5dde89-7f9b-4b9c-8669-1f0425b2b55d") {
id
flatData {
body
slug
subtitle
title
}
}
}`
return await graphQLClient.request(query);
}
BlogPost is a model having the types :
import { Field, ObjectType } from '#nestjs/graphql';
import { BaseModel } from './base.model';
import FlatDateType from '../resolvers/blogPost/types/flatDatatype.type';
#ObjectType()
export class BlogPost extends BaseModel {
#Field({ nullable: true })
id!: string;
#Field((type) => FlatDateType)
flatData: FlatDateType;
}
and FlatDateType has the following code
export default class FlatDateType {
body: string;
slug: string;
subtitle: string;
title: string;
}
it throws the following exception :
Error: Cannot determine a GraphQL output type for the "flatData". Make
sure your class is decorated with an appropriate decorator.
What is missing in here?
How is your graphql server supposed to understand the type of FlatDataType when there's no information about it being passed to the graphql parser? You need to add the graphql decorators to it as well. #ObjectType(), #Field(), etc.
FlatDataType is not defined as #ObjectType(), therefore type-graphql (or #nestjs/graphql) can't take it as an output in GraphQL.
Learning NestJs actually and facing an issue saving typeorm OneToMany relation.
Let's say I have two modules ProjectsModule # PlansModule
Exists a OneToMany relation between Plan & Project entities
#Entity()
export class Project extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
...
#OneToMany(type => Plan, plan => plan.project, { eager: true })
plans: Plan[];
}
#Entity()
export class Plan extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
...
#ManyToOne(type => Project, project => project.plans, { eager: false } )
project: Project;
#Column()
projectId: string;
}
In the ProjectsModule, I have a ProjectsService with this method:
async getProjectById(
id: string,
user: User
): Promise<Project> {
const found = await this.projectRepository.findOne({ where: { id, ownerId: user.id } });
if(!found) {
throw new NotFoundException(`Project with ID "${id}" not found`)
}
return found;
}
My problem is when I try to save a new Plan.
My PlansService calls the PlanRepository like that
async createPlan(
createPlanDto: CreatePlanDto,
user: User
): Promise<Plan> {
return this.planRepository.createPlan(createPlanDto, user);
}
And on the PlanRepository :
constructor(
#Inject(ProjectsService)
private projectsService: ProjectsService
) {
super();
}
async createPlan(
createPlanDto: CreatePlanDto,
user: User
): Promise<Plan> {
const { title, description, project } = createPlanDto;
const plan = new Plan();
const projectFound = await this.projectsService.getProjectById(project, user)
plan.title = title;
plan.description = description;
plan.status = PlanStatus.ENABLED;
plan.owner = user;
plan.project = project;
try {
await plan.save();
} catch (error) {
this.logger.error(`Failed to create a Plan for user "${user.email}". Data: ${JSON.stringify(createPlanDto)}`, error.stack);
throw new InternalServerErrorException();
}
delete plan.owner;
return plan;
}
Trying this throws me this error when sending a POST request to my plan controller :
TypeError: this.projectsService.getProjectById is not a function
And trying a
console.log('service', this.projectsService)
give me
service EntityManager {
repositories: [],
plainObjectToEntityTransformer: PlainObjectToNewEntityTransformer {},
connection: Connection {
I guess I'm not using the projectsService properly but I don't understand where I could have made a mistake.
On the module's side I'm exporting the ProjectsService in his module:
exports: [ProjectsService]
And importing the full ProjectsModule into the PlansModule:
imports: [
TypeOrmModule.forFeature([PlanRepository]),
AuthModule,
ProjectsModule
],
Sorry for the long post, trying to be exhaustive.
import { Injectable, NotFoundException } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { User } from '../auth/user.entity';
import { PlanRepository } from './plan.repository';
import { GetPlanFilterDto } from './dto/get-plan-filter.dto';
import { Plan } from './plan.entity';
import { CreatePlanDto } from './dto/create-plan.dto';
#Injectable()
export class PlansService {
constructor(
#InjectRepository(PlanRepository)
private planRepository: PlanRepository,
) {}
async getPlans(filterDto: GetPlanFilterDto, user: User): Promise<Plan[]> {
return this.planRepository.find({ ...filterDto, ownerId: user.id });
}
async getPlanById(id: string, user: User): Promise<Plan> {
return this.planRepository.findOne({
where: { id, ownerId: user.id },
});
}
async createPlan(createPlanDto: CreatePlanDto, user: User): Promise<Plan> {
const { project, ...data } = createPlanDto;
return this.planRepository
.create({
projectId: project,
ownerId: user.id,
...data,
})
.save();
}
}
This PlanService only uses the internal methods of the Repository, if you're logging in the event of an Error, ExceptionFilter would be a suitable option for this: https://docs.nestjs.com/exception-filters.
Instead of checking if the plan had been found, you can use an interceptor:
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
NotFoundException,
} from '#nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
#Injectable()
export class PlanNotFoundInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(plan => {
if (!plan) {
throw new NotFoundException("plan couldn't be found");
}
return plan;
}),
);
}
}
Then on your getById (Controller) use #UseInterceptor, this decouples your service, data access, logging, validation, etc..
I've simplified the implementation (for Interceptor), you may need to adjust it slightly to suit your exact need.
yarn run v1.22.4
$ jest
ts-jest[versions] (WARN) Version 24.9.0 of jest installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=25.0.0 <
26.0.0). Please do not report issues in ts-jest if you are using unsupported versions.
ts-jest[versions] (WARN) Version 24.9.0 of jest installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=25.0.0 <
26.0.0). Please do not report issues in ts-jest if you are using unsupported versions.
ts-jest[versions] (WARN) Version 24.9.0 of jest installed has not been tested with ts-jest. If you're experiencing issues, consider using a supported version (>=25.0.0 <
26.0.0). Please do not report issues in ts-jest if you are using unsupported versions.
PASS src/auth/user.repository.spec.ts
PASS src/projects/projects.service.spec.ts
PASS src/auth/jwt.strategy.spec.ts
PASS src/auth/user.entity.spec.ts
Test Suites: 4 passed, 4 total
Tests: 18 passed, 18 total
Snapshots: 0 total
Time: 3.774s, estimated 4s
Ran all test suites.
Done in 4.58s.
I haven't spent much time reviewing your tests, but the changes made haven't made any breaking changes to the unit tests (can't say the same for e2e, personally don't use Cucumber.js).
The point of this answer isn't to provide you the code needed, but the abstractions you can use to solve the tightly coupled components.
You can also use the Interceptor to validate the request, check if a project is present, check if it exists, if not abort with error. Again decoupling your error handling from your controller/service/whatever.
You also have the option to pluck/add things to the request, for example a .user that's authenticated, or a value from a header. (Could be useful if you want to send the projectId into the Controller via the Request object).
I am trying to create a config.json file with just a few urls and read the data in a Service.
.../.../assets/config.json
{
"registration" : "localhost:4200/registration"
"login" : "localhost:4200/login"
}
../services/config.service.ts
export class ConfigService {
result;
configUrl = '../../assets/config.json';
constructor(private http: HttpClient) {}
getConfig() {
return this.http.get(this.configUrl).subscribe((data) => { this.result = data });
}
}
In the specific login.service.ts and registration.service.ts I call the getConfig() to handle the specific urls. The problem is that in this services the return value is undefined but I need the result out of the subscribe/getConfig method.
Now I am watching about 3 hours for a solution but I do get much more confused as more as I read so I would like to ask for help.
I saw solutions with .map() method (but I guess this method does not exist anymore), with "Promises", with export interface but nothing worked.
example for ../registration.service.ts
export class RegistrationService {
private api_url;
constructor(private http: HttpClientModule, private cs: ConfigService) {
this.api_url = this.cs.getConfig();
}
}
Your Config Service:
getConfig() {
return this.http.get(this.configUrl).toPromise();
}
Your Registration Service :
async exampleMethod() {
try {
this.api_url = await this.cs.getConfig();
console.log(this.api_url);
} catch(e) {
throw Error(e);
}
}
I've checked the document and source code for pagination implementation (advanced-example-server.component.ts).
And found that the ServerDataSource it used had only implemented pagination via HTTP GET (_sort, _limit, _page, etc parameters expose in URL)..... as my current project worked on required to use POST to send front-end parameters to back-end Restful APIs,
using extends to HTTP post call implement, I don't know how to add the extra parameters in pagination request. I Need To pass the request_server to extendsplugin.ts
import { Observable } from 'rxjs/internal/Observable';
import { ServerDataSource } from 'ng2-smart-table';
export class PostServerDataSource extends ServerDataSource {
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
return this.http.post(this.conf.endPoint, request_server, { observe: 'response' });
}
}
anotherComponent.ts
swiftListTable() {
const request_server = { "userType": this.currentUser.role, "authName": this.currentUser.username }
this.source = new PostServerDataSource(this.http,{endPoint: this.service.apiURL + 'swift/pagination', dataKey: 'content', pagerLimitKey:"_limit",
pagerPageKey:"_page",
sortDirKey: "pageable",
sortFieldKey: "pageable",
totalKey:'totalElements'});
}
There are two ways you can handle it,
one way is attaching the params in query string and append to url like,
this.service.apiURL + 'swift/pagination?param1=p¶m2=q'
Other way could be handling it in requestElements and swiftListTable functions like below.
swiftListTable() {
const request_server = {
"userType": this.currentUser.role,
"authName": this.currentUser.username
}
this.source = new PostServerDataSource(http,
{ endPoint: url, dataKey: 'content', pagerLimitKey:'_limit'}, request_server);
export class PostServerDataSource extends ServerDataSource {
params: any;
constructor(http: HttpClient, config: any, params?: any) {
super(http, config);
this.params = params;
}
protected requestElements(): Observable<any> {
let httpParams = this.createRequesParams();
if (this.params) {
let keys = Object.keys(this.params);
keys.forEach((key) => {
httpParams = httpParams.set(key, this.params[key]);
});
}
return this.http.post(this.conf.endPoint, httpParams, { observe: 'response' });
}
}