I can't get the class-validator to work. It seems like I am not using it: everything works as if I didn't use class-validator. When sending a request with an incorrectly formatted body, I don't have any validation error, although I should.
My DTO:
import { IsInt, Min, Max } from 'class-validator';
export class PatchForecastDTO {
#IsInt()
#Min(0)
#Max(9)
score1: number;
#IsInt()
#Min(0)
#Max(9)
score2: number;
gameId: string;
}
My controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastDTO: PatchForecastDTO[],
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastDTO);
}
My bootstrap:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe());
await app.listen(PORT);
Logger.log(`Application is running on http://localhost:${PORT}`, 'Bootstrap');
}
bootstrap();
I can't find what's wrong. What did I miss?
In the current version of NestJS (7.6.14), validating a request body that is a JSON array is supported using the built in ParseArrayPipe.
#Post()
createBulk(
#Body(new ParseArrayPipe({ items: CreateUserDto }))
createUserDtos: CreateUserDto[],
) {
return 'This action adds new users';
}
See the official docs or the source code for more info.
NestJS actually does not support array validation out of the box. In order to validate an array, it must be wrapped in an object.
This way, I would not use a DTO corresponding to a list of items, but a DTO corresponding to an object that contains a list of items:
import { PatchForecastDTO } from './patch.forecast.dto';
import { IsArray, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
export class PatchForecastsDTO {
#IsArray()
#ValidateNested() // perform validation on children too
#Type(() => PatchForecastDTO) // cast the payload to the correct DTO type
forecasts: PatchForecastDTO[];
}
And I would use that DTO in my controller:
#Patch('/:encid/forecasts/updateAll')
async updateForecast(
#Body() patchForecastsDTO: PatchForecastsDTO,
#Param('encid') encid: string,
#Query('userId') userId: string
): Promise<ForecastDTO[]> {
return await this.instanceService.updateForecasts(userId, encid, patchForecastsDTO);
}
Related
I am not too confident working with Firestore and have trouble with more complex API calls to get data. Usually I use SQL backends in my apps.
For the section that I am working on, I would like to combine three collections to get an array of ToDos with the involved users and the category the current user labelled this ToDo with. Every involved person can label the ToDo like they prefer, which makes things a little more complicated. Broken down the collections are structured as follows.
todo: Firestore Database Document
{
title: string,
involved: string[], //user ids
involvedCategory: string[] //category ids mapped by index to involved
}
(I tried to have an array of objects here instead of the two arrays, but it seems I would not be able to query the array for the current user´s ID, like mentioned here, so this is a workaround)
category: Firestore Database Document
{
title: string,
color: string
}
user: Firebase Authentication User
{
uid: string,
displayName: string,
photoURL: string,
...
}
THE GOAL
An array of ToDo items like this:
{
id: string,
title: string,
involved: User[],
category?: {
title: string,
color: string
}
}
As I am working with TypeScript, I created an interface to use a converter with. My code looks like this so far:
import {
DocumentData,
FirestoreDataConverter,
WithFieldValue,
QueryDocumentSnapshot,
SnapshotOptions,
query,
collection,
where,
} from 'firebase/firestore'
import { store } from '../firebase'
import { useCollectionData } from 'react-firebase-hooks/firestore'
import { User } from 'firebase/auth'
import { useCategories } from './categories'
import { useAuth } from '../contexts/AuthContext'
interface ToDo {
id: string
title: string
involved: User[]
category?: {
title: string
color: string
}
}
const converter: FirestoreDataConverter<ToDo> = {
toFirestore(todo: WithFieldValue<ToDo>): DocumentData {
return {} //not implemented yet
},
fromFirestore(
snapshot: QueryDocumentSnapshot,
options: SnapshotOptions
): ToDo {
const data = snapshot.data(options)
return {
id: snapshot.id,
title: data.title,
category: undefined, //?
involved: [], //?
}
},
}
export function useToDos() {
const { currentUser } = useAuth()
const { categories } = useCategories() //needed in converter
const ref = query(
collection(store, 'habits'),
where('involved', 'array-contains', currentUser.uid)
).withConverter(converter)
const [data] = useCollectionData(ref)
return {
todos: data,
}
}
Is there any way I can do this? I have a Hook that returns all of the user´s categories, but I obviously can´t call that outside the
useToDos-Hook. And creating the const in the hook does not help, either, as it results in an infinite re-render.
I know this is a long one, but does anyone have tips how I could approach this? Thanks in advance ^^
UPDATE:
I had to make two small adjustments to #ErnestoC ´s solution in case anyone is doing something similar:
First, I changed the calls for currentUser.id to currentUser.uid.
Afterwards I got the very missleading Firestore Error: PERMISSION_DENIED: Missing or insufficient permissions, which made me experiment a lot with my security rules. But that is not where the error originated. Debugging the code line by line, I noticed the category objects resolved by the promise where not correct and had a weird path with multiple spaces at the beginning and the end of their ids. When I removed them before saving them in the promises array, it worked. Although I do not see where the spaces came from in the first place.
promises.push(
getDoc(
doc(
store,
'categories',
docSnap.data().involvedCategory[userCatIndex].replaceAll(' ', '')
)
)
)
The general approach, given that Firestore is a NoSQL database that does not support server-side JOINS, is to perform all the data combinations on the client side or in the backend with a Cloud Function.
For your scenario, one approach is to first query the ToDo documents by the array membership of the current user's ID in the involved array.
Afterwards, you fetch the corresponding category document the current user assigned to that ToDo (going by index mapping between the two arrays). Finally, you should be able to construct your ToDo objects with the data.
const toDoArray = [];
const promises = [];
//Querying the ToDo collection
const q = query(collection(firestoreDB, 'habits'), where('involved', 'array-contains', currentUser.id));
const querySnap = await getDocs(q);
querySnap.forEach((docSnap) => {
//Uses index mapping
const userCatIndex = docSnap.data().involved.indexOf(currentUser.id);
//For each matching ToDo, get the corresponding category from the categories collection
promises.push(getDoc(doc(firestoreDB, 'categories', docSnap.data().involvedCategory[userCatIndex])));
//Pushes object to ToDo class/interface
toDoArray.push(new ToDo(docSnap.id, docSnap.data().title, docSnap.data().involved))
});
//Resolves all promises of category documents, then adds the data to the existing ToDo objects.
await Promise.all(promises).then(categoryDocs => {
categoryDocs.forEach((userCategory, i) => {
toDoArray[i].category = userCategory.data();
});
});
console.log(toDoArray);
Using the FirestoreDataConverter interface would not be that different, as you would need to still perform an additional query for the category data, and then add the data to your custom objects. Let me know if this was helpful.
So I'm trying to fill a select component with a enum type from mongoose
In my user service the schema looks something like :
firstName: { type:String, required: true },
...
ris:{type: String, default: 'R', enum:['R', 'I', 'S']},
In my feathers service I can access the Model with "this.Model"
so in any hook I can do:
this.Model.schema.path('ris').enumValues); //['R','C','I']
and I get the values from the enum type.
Now since I can't create custom API methods other that the officials ones
Feathers calling custom API method
https://docs.feathersjs.com/clients/readme.html#caveats
https://docs.feathersjs.com/help/faq.html#can-i-expose-custom-service-methods
How can I create a service method/call/something so that I can call it in my
componentDidMount(){ var optns= this.props.getMyEnumsFromFeathers}
and have the enum ['R','C','I'] to setup my dropdown
I'm Using React/Redux/ReduxSaga-FeathersJS
I'd create a service for listing Enums in the find method:
class EnumService {
find(params) {
const { service, path } = params.query;
const values = this.app.service(service).Model.schema.path(path).enumValues;
return Promise.resolve(values);
}
setup(app) {
this.app = app;
}
}
app.use('/enums', new EnumService())
Then on the client you can do
app.service('enums').find({ query: {
service: 'myservice',
path: 'ris'
}
}).then(value => console.log('Got ', values));
I was trying to use this code, but, it does not work like plug and play.
after some play with the app service I figured out the code below
async find(params) {
const { service, path } = params.query;
const values = await this.app.service(service).Model.attributes[path].values;
return values || [];
}
setup(app) {
this.app = app;
}
I am not sure if it is a thing of what database is been used, in my case I am in development environment, so, I am using sqlite.
I'need to access the $promise property of object returned with some default action on $resource (e.g. remove, query...). But it seems it is not possible in current TypeScript definitions for $resource:
Example:
I have a student resource:
interface IStudentDataModel {
name: string
age: number
}
interface IStudentResource extends IResourceClass<IStudentDataModel> {
deactivate(params: Object): IResource<IStudentDataModel>
}
export default function ($resource: IResourceService, CONFIG: IConfig): IStudentResource {
"ngInject"
let deactivateAction: IActionDescriptor = {
url: `${CONFIG.api_host}/students/:studentId/deactivate`,
method: 'PUT'
}
return <IStudentResource>$resource(
`${CONFIG.api_host}/students/:studentId`,
}
studentId: '#studentId'
},
{
deactivate: deactivateAction,
});
}
Then I'm using this resource in another service:
export default class StudentActions {
constructor(
private StudentResource: IStudentResource,
) {
"ngInject"
}
deactivate(studentId: number): IPromise<any> {
return this.StudentResource.deactivate({ studnetId: studentId}).$promise;
}
remove(studentId: number): IPromise<any> {
return this.StudentResource.remove({ studentId: studentId}).$promise; //!!!
}
}
PROBLEM: I can't access to the this.StudentResource.remove({ studentId: studentId}).$promise because default actions have IStudentDataModel as return trype instead of IResource<IStudentDataModel> (but it returns resource with $promise property, not only data model).
I've tried to override the remove method in IStudentResource:
interface IStudentResource extends IResourceClass<IStudentDataModel> {
deactivate(params: Object): IResource<IStudentDataModel>
remove(params: Object): IResource<IStudentDataModel>
}
But compiler says:
Interface 'IStudentResource' incorrectly extends interface
'IResourceClass'. Types of property 'remove' are
incompatible.
Type '(data: Object) => IResource' is not assignable to type 'IResourceMethod'.
It's a bit crude but you can use a type assertion to any in order to make the type checking stop complaning.
return (<any>this.StudentResource.deactivate({ studnetId: studentId})).$promise;
Also since the typescript interfaces are open ended you can also amend the IResource interface to add the $promise property.
interface IResourceClass<T> {
$promise: IPromise<T>
}
I'm working with Angular 2 and Ionic 2, trying to build an array of reactive objects.
I'm trying to figure out the correct way to build an ion-list component (StocksList) that populates itself via a simple service (StocksListService) that instantiates new observable (Stock) class instances which get their data from a service (StockService) that calls an API repeatedly on an Observable.interval.
Ultimately I want to be able to create a stateful pipe to sort the Stocks[] (and I want to understand the actual correct way to do what I'm doing).
I don't really know how to explain my intention concisely any better than that, so I'm hoping my code will help clarify the situation:
——
Home Page
^
Stocks List Component < Stocks List Service
^
Stock Component < Stock Service
——
Home Page—
home.html
<ion-content>
<ion-list
stocksListCmp ></ion-list>
</ion-content>
home.ts
#Page({
templateUrl: 'build/pages/home/home.html',
directives: [StocksList],
providers: [StocksListService]
})
export class HomePage {
constructor(public nav: NavController) { }
}
Stocks List Component—
stocks-list.tpl.html
<ion-card
[stockCmp]="stock"
*ngFor="#stock of stocks; #i = index"></ion-card>
stocks-list.component.ts
#Component({
selector: '[stocksListCmp]',
viewProviders: [StocksListService, StockService]
})
#View({
templateUrl: 'build/components/stocks-list/stocks-list.tpl.html',
directives: [Stock]
})
export class StocksList {
stocks: Observable<IStock>[]; // not sure if my typing is correct here, or anywhere with advanced types really
constructor(private stocksListService: StocksListService) { }
ngOnInit() {
this.stocks = this.stocksListService.stocks;
}
}
stocks-list.service.ts
let tickers: string[] = ['AAPL', 'BTCUSD=X', '^DJI', '^GSPC', 'NFLX', 'TSLA'];
#Injectable()
export class StocksListService {
constructor( #Inject(forwardRef(() => StockService)) private stockService: StockService) { }
get stocks(): Observable<IStock>[] {
return tickers
.map((ticker) => new Stock(this.stockService.init(ticker))); // getting type error on this.stockService.init(ticker): Observable<Observable<IStock>> not assignable to StockService
}
}
Stock Component—
stock.tpl.html
<ion-card-content>
<ion-row>
<ion-col>{{ stock?.ticker }}</ion-col>
<ion-col>{{ stock?.price }}</ion-col>
<ion-col>{{ stock?.chg_percent}}</ion-col>
</ion-row>
</ion-card-content>
stock.component.ts
#Component({
selector: '[stockCmp]',
inputs: ['stockCmp'],
viewProviders: [StockService]
})
#View({
templateUrl: 'build/components/stock/stock.tpl.html'
})
export class Stock {
stockCmp: Stock; // not sure how to type here
stock: IStock; // not sure how to type here
constructor(private stockService: StockService) { }
ngOnInit() {
this.stockCmp['stockService']['init']
.subscribe((data) => this.stock = data,
null,
() =>
this.stockCmp['stockService']['update']
.subscribe((service) => service
.subscribe((data) => this.stock = data))
);
}
}
stock.service.ts
let source: Observable<number> = Observable.interval(60 * 1000).publish().refCount();
#Injectable()
export class StockService {
constructor(private http: Http) { }
public init(ticker: string): Observable<Observable<IStock>> {
if (ticker) {
let url = 'http://finance.yahoo.com/webservice/v1/symbols/' + ticker + '/quote?format=json&view=detail';
return this.updateData(url);
}
}
private loadData(url: string): Observable<IStock> {
return this.http.get(url)
.map((res) => this.mapData(res.json().list.resources[0].resource.fields));
}
private mapData(data: any): IStock {
return {
ticker: data.symbol,
name: data.name,
price: JSON.parse(data.price),
chg_percent: JSON.parse(data.chg_percent) / 100,
ts: data.ts
};
}
public updateData(url: string): Observable<Observable<IStock>> {
return { init: this.loadData(url), update: source.map(() => this.loadData(url)) }; // type error
}
private logError(error) {
console.log(error);
}
}
stock.d.ts
export interface IStock {
ticker: string;
name: string;
price: number;
chg_percent: number;
ts: number;
}
So this actually works pretty well right now but I'm certain it's not the correct way to do it. There has to be a better way to get the results from the loadData method before the first interval call.
I'd like to understand the correct way to do this in regards to building the ion-list, instantiating new class instances, and using reactive extensions and hopefully that will help me implement a stateful pipe to sort the stocks list.
Any advice is much appreciated! If I've left anything out or need to clarify anything, please let me know. Thanks!
I currently implemented an angular $http interceptor adding custom headers to requests based on a localstorage value (I need to implement a "Su" feature in my app)
I need to "deactivate" this behaviour on some special requests (=I need to be able to configure this on a per-request basis), and I'd like to do this by putting an extra config parameter denoting this when calling my $http methods.
The interceptor is looking like this :
$httpProvider.interceptors.push((localStorageService: ng.local.storage.ILocalStorageService) => {
return {
request: (config: ng.IRequestShortcutConfig) => {
var su = localStorageService.get<string>('Su');
if(su && !("avoidSudo" in config)){
config.headers.Su = `{ "principal": "${su}" }`;
}
return config;
}
}
});
And the $http service call is looking like this when I want to deactivate my "su" feature :
this.$http.get('/api/sessions/current', { avoidSudo: true })
In typescript 1.6, this code doesn't compile as $http.get()'s second argument is expected to be a ng.IRequestShortcutConfig which obviously doesn't contain my specific avoidSudo field (Typescript compilation error is perfectly right here)
I can workaround the compilation error by replacing the { avoidSudo: true } by <any>{ avoidSudo: true } but this is clearly not ideal in terms of typesafety
I tried to create a new SkipSudoRequestShortcutConfig dedicated class (implementing ng.IRequestShortcutConfig) for this purpose. Something like this :
module mymodule {
export class SkipSudoRequestShortcutConfig implements ng.IRequestShortcutConfig {
// For testing purposes only
_noSudo: boolean;
constructor(
public params?: any,
public headers?: any,
public xsrfHeaderName?: string,
public xsrfCookieName?: string,
public cache?: any,
public withCredentials?: boolean,
public data?: any,
public transformRequest?: any,
public transformResponse?: any,
public timeout?: any,
public responseType?: string
){
this._noSudo = true;
}
}
}
called like this :
var config = new SkipSudoRequestShortcutConfig();
console.log(config instanceof SkipSudoRequestShortcutConfig); // Shows true
console.log(config._noSudo); // Shows true
this.$http.get('/api/sessions/current', config)
and used like this in the interceptor :
request: (config: ng.IRequestShortcutConfig) => {
var su = localStorageService.get<string>('Su');
console.log(config instanceof SkipSudoRequestShortcutConfig); // Shows *false* :(
// console.log(config._noSudo); // Doesn't compile, but if executed at runtime with a breakpoint, it works and display true
if(su && !(config instanceof mymodule.SkipSudoRequestShortcutConfig)){
config.headers.Su = `{ "principal": "${su}" }`;
}
return config;
}
but once in the request handler, the instanceof test was falsy.
I was wondering what would be the best/simplest way to achieve this goal.
Do you think the ng.IRequestShortcutConfig is missing a special config field allowing to put custom fields from $http invocations to interceptor handlers ?
Thanks in advance,
It's important to remember that types in TypeScript are just "type helpers" and removed when transpiled to javascript. So you don't need to implement a new $http service. Instead you can just create a new type that suites your needs.
In reality it's because the angular type definition is lacking.
You can fix this by creating the following interfaces.
export interface IRequestShortcutConfigWithCustomConfig extends ng.IRequestShortcutConfig {
[key: string]: any;
}
I'm using a dictionary style type cause that'll support everything. But you could change that to avoidSudo: boolean; if you don't want a general definition.
export interface IHttpServiceWithCustomConfig extends ng.IHttpService {
get<T>(url: string, config?: IRequestShortcutConfigWithCustomConfig): ng.IHttpPromise<T>;
}
Then in your controller you just use the new interface.
constructor(private $http: IHttpServiceWithCustomConfig) {
$http.get("/api/sessions/current", { avoidSudo: true }
}
You do the exact same with IHttpInterceptor
export interface IRequestConfigWithCustomConfig extends ng.IRequestConfig, IRequestShortcutConfigWithCustomConfig {
}
export interface IHttpInterceptorWithCustomConfig extends ng.IHttpInterceptor {
request?: (config: IRequestConfigWithCustomConfig) => IRequestConfigWithCustomConfig | ng.IPromise<IRequestConfigWithCustomConfig>;
}