In NestJS I want to use PickType() with a class, which has nested class properties.
Example:
export class Product {
status: string;
payment: {
status: string;
type: string;
}
}
It should result in the swagger documentation as following DTO:
class PartialClass {
payment: {
status: string,
type: string
}
}
But the following implementation does not work:
class PartialClass {
#ApiProperty({
type: () => PickType(Videochat, ['payment']),
})
payment: Pick<Videochat['payment'], 'type' | 'status'>;
}
It returns an empty payment class property:
class PartialClass {
payment: {
}
}
So i guess PickType can't handle nested Types.
I tried also:
type: () => PickType(PickType(Videochat, ['payment']), ['status', 'type'])
but it also not working.
You can handle it this way, let's assume we have the following class:
type User = {
id: number,
name: string,
address: {
street: string,
zipcode: string,
geo: {
lat: string,
lng: string,
},
},
};
to pick user with address you can simply do type UserWithOnlyAddress = Pick<User, 'address'>;
in case you have to pick user with address's street only you can handle it:
type Pick2<T, K1 extends keyof T, K2 extends keyof T[K1]> = {
[P1 in K1]: {
[P2 in K2]: (T[K1])[P2];
};
};
type UserWithOnlyStreetAddress = Pick2<User, 'address', 'street'>;
And for picking at three levels
type UserWithOnlyGeoLat = Pick3<User, 'address', 'geo', 'lat'>;
type User = {
id: number,
name: string,
address: {
street: string,
zipcode: string,
geo: {
lat: string,
lng: string,
},
},
};
type Pick3<T, K1 extends keyof T, K2 extends keyof T[K1], K3 extends keyof T[K1][K2]> = {
[P1 in K1]: {
[P2 in K2]: {
[P3 in K3]: ((T[K1])[K2])[P3];
};
};
};
Related
Inside #auth allow or where field, I would like to access the role of the user on PART_OF_TEAM to determine whether or not they should read/edit the posts.
As an example: a user connected to a team with the edge property "VIEWER" should only be able to read the posts inside the team, not edit them.
I am using neo4j graphql javascript plugin and I have these type definitions:
type Post #auth(
rules: [
{
operations: [READ]
allowUnauthenticated: true
allow: {
OR:[
{ visibility: PUBLIC },
{ users: { id: "$jwt.id"} }, # users reference the Post users field
{ inTeam: {users: {id: "$jwt.id"}}},
]
},
},
])
{
id: ID! #id
name: String!
"See Enum ANCHOR Visibility: PRIVATE or PUBLIC"
visibility: Visibility!
description: String
color: String!
inTeam: [Team!]! #relationship(type: "IN_TEAM", direction: OUT)
}
type User {
"generate unique id"
id: ID! #id
username: String
email: String!
password: String!
inTeams: [Team!]!
#relationship(
type: "PART_OF_TEAM"
properties: "PartOfTeam"
direction: OUT
)
}
type Team {
id: ID! #id
name: String!
users: [User!]!
#relationship(
type: "PART_OF_TEAM"
properties: "PartOfTeam"
direction: IN
)
}
interface PartOfTeam #relationshipProperties {
role: Role!
}
I already tried to search for an answer but only found this other question which was not answered:
neo4j #auth related to edge property
I have this route which can return one of these two different DTOs:
#Get()
#ApiQuery({ name: 'legacy', description: "'Y' to get houses legacy" })
async findAllHouses(
#Query('legacy') legacy: string,
): Promise<HousesDto[] | HousesLegacyDto[]> {
...
}
I want to display both of these ResponseDTOs in swagger.
I've tried this decorator:
#ApiOkResponse({
schema: { oneOf: refs(HousesDto, HousesLegacyDto) },
})
// OR
#ApiOkResponse({
schema: {
oneOf: [
{ $ref: getSchemaPath(HousesDto) },
{ $ref: getSchemaPath(HousesLegacyDto) },
],
},
})
with #ApiExtraModels() on top of DTO classes and #ApiProperty() on each properties.
But I still get empty objects in Swagger and I suppose it would not have even taken array types in consideration.
How can I display both of these schemas properly?
Seems to me like a lot of very obscure solutions have been posted here and there, so I will try to clarify what needs to be done.
You have two DTOs:
export class SomeStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Status',
example: 'in_progress',
})
#IsString()
status: string;
}
export class ErrorStatusDto {
#ApiProperty({
description: 'Id',
example: 1,
})
#IsNumber()
id: number;
#ApiProperty({
description: 'Error',
example: 'Some error string',
})
#IsString()
error: string;
}
Then you have your controller:
#UseGuards(AccountTypesGuard)
#ApiOperation({ summary: 'Get status of...' })
#Get('status')
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
async getPullStatus(
#Request() req,
#Param('id', ParseIntPipe) someId: number,
): Promise<SomeStatusDto | ErrorStatusDto> {
// check if someId belongs to user
const idBelongsToUser = await this.myService.validateSomeId(
req.user.id,
someId,
);
if (!idBelongsToUser) {
throw new ForbiddenException(
`SomeId does not belong to user (someId=${someId}, userId=${req.user.id})`,
);
}
const key = `status-${someId}`;
const response = await this.redisService.getByKey(key);
return response ? response : {};
}
Note the solution below. You need to reference the DTOs as #ApiExtraModels() and then you can add them as anyOf: refs(...) in your schema.
#ApiExtraModels(SomeStatusDto, ErrorStatusDto)
#ApiOkResponse({
schema: { anyOf: refs(SomeStatusDto, ErrorStatusDto) },
})
Hope this helps somebody :)
so I encountered a similar issue and this is how you could get the output shown in the image above.
Using the #ApiResponse decorator you could set the two responses using the examples property, try the code sample below
#ApiResponse({
status: 200,
description: 'Successful response',
content: {
'application/json': {
examples: {
HousesDto: { value: HousesDto },
HousesLegacyDto: { value: HousesLegacyDto },
},
},
},
})
I have a Patient entity:
#Entity()
#ObjectType()
#InputType('PatientInput')
export class Patient extends BaseEntity {
#PrimaryGeneratedColumn()
#Field(type => Int, { nullable: true })
id: number
#Column({ length: 500 })
#Field({ nullable: true })
firstName?: string
#Column({ length: 500 })
#Field({ nullable: true })
lastName?: string
#Field(type => MedicalCondition, { nullable: true })
#OneToMany(type => MedicalCondition, medicalCondition => medicalCondition.patient, { cascade: true })
medicalConditions?: MedicalCondition[]
}
And a MedicalCondition entity:
#ObjectType()
#Entity()
#InputType('medicalCondition')
export class MedicalCondition extends BaseEntity {
#Field({nullable: true})
#PrimaryGeneratedColumn()
id: number
#Field(type => Int, { nullable: true })
#RelationId((medicalCondition: MedicalCondition) => medicalCondition.patient)
#Column()
patientId?: number
#Field(type => Patient, { nullable: true })
#ManyToOne(type => Patient, patient => patient.medicalConditions, {
onDelete: 'CASCADE',
})
#JoinColumn()
patient?: Patient
#Field()
#Column()
name: string
#Field({nullable: true})
#Column()
startDate?: Date
}
When trying to create an instance of patient using the BaseEntity create function, the patient is created but in the medical conditions array only containes the last element and all the rest disappear even if there were many elements.
#Mutation(returns => Patient)
async create(#Args('patient') newPatientInput: Patient, #CurrentUser() user: User, #Useragent() useragent): Promise<Patient> {
const newPatient: Patient = Patient.create(newPatientInput)
const event = createEvent({ useragent, actionType: 'add_patient', userId: user.id })
const p = await this.patientService.create(newPatient, event)
return p
}
create = async (patient: Patient, event: EventLog): Promise<Patient> => {
const isExist = await this.isPatientExist({ firstName: patient.firstName, lastName: patient.lastName, birthDate: patient.birthDate })
if (isExist > 0) {
throw new Error('Patient already exist')
} else {
const transactionResult = runTransaction(async (em) => {
const eventLog = EventLog.create(event)
await em.save(eventLog)
return await em.save(patient)
})
return transactionResult
}
}
I tried to directly save the entity without invoting create():
return await em.save(Patient, patient)
and that was the result of the saving:
medicalConditions:
[ { id: 36,
name: 'heartDisease',
startDate: 2016-07-31T21:00:00.000Z,
kin: 'father',
note: '',
patientId: 26,
createdAt: 2019-11-24T17:11:22.376Z,
updatedAt: 2019-11-24T17:11:22.376Z },
{ id: null,
name: 'previousHeartAttack',
startDate: 2018-04-30T21:00:00.000Z,
kin: 'brother',
note: '' },
{ id: null,
name: 'highBloodPressure',
startDate: 2018-03-31T21:00:00.000Z,
kin: 'sister',
note: '' } ],
Tried google for it and didn't find any known issue.
So the willing result would be to create an entity deeply, is that a possible behavior?
i have a comments connection in a mutation, this is the query:
export default mutationFromQuery(graphql`
mutation AddBookMutation($input: AddBookInput! $count: Int $cursor: String ) {
addBook(input: $input) {
book {
__typename
cursor
node {
id
title
owner
createdAt
comments(first: $count, after: $cursor)
#connection(key: "BookComments_comments", filters: []) {
__typename
edges {
node {
id
}
}
}
}
}
}
}
`)
This is how i did my optimisticUpdater that don't work:
optimisticUpdater: (store) => {
const userProxy = store.get(viewerId)
const owner = userProxy.getValue('username')
const id = uuidv1();
const book = store.create(id, 'Book');
book.setValue(id, 'id');
book.setValue(bookTitle, 'title');
book.setValue(owner, 'owner');
book.setValue(Date.now(), 'createdAt');
const comments = store.create(uuidv1(), 'comments')
comments.setLinkedRecords([], 'edges')
const pageInfo = store.create(uuidv1(), 'pageInfo')
pageInfo.setValue(null, 'endCursor')
pageInfo.setValue(false, 'hasNextPage')
pageInfo.setValue(false, 'hasPreviousPage')
pageInfo.setValue(null, 'startCursor')
comments.setLinkedRecord(pageInfo, 'pageInfo')
book.setLinkedRecord(comments, 'comments')
const bookEdge = store.create(uuidv1(), 'BookEdge');
bookEdge.setLinkedRecord(book, 'node');
console.log('bookEdge ', bookEdge)
booksUpdater(userProxy, bookEdge);
},
The problem i have is that comments always ends up on undefined as you can see above i've already set it. I also did this but i am still not getting an optimistic UI:
optimisticResponse: {
addBook: {
book: {
__typename: 'BookEdge',
cursor: uuidv1(),
node: {
id: uuidv1(),
title: bookTitle,
owner: username,
createdAt: Date.now(),
comments: {
__typename: 'CommentConnection',
edges: [],
pageInfo: {
endCursor: null,
hasNextPage: false
}
}
}
}
}
},
App don't crash with optimisticResponse code but no optimistic UI effect, but with the optimisticUpdater it's crashing with comments being undefined, for now I am settling with my updater:
updater: (store) => {
const userProxy = store.get(viewerId)
const payload = store.getRootField('addBook');
booksUpdater(userProxy, payload.getLinkedRecord('book'));
},
since the comments is undefined I guess we cannot use this for optimistic effect:
const comments = store.create(uuidv1(), 'comments')
comments.setLinkedRecords([], 'edges')
book.setLinkedRecord(comments, 'comments')
on my Book, this is the query which has the comments fragment that is undefined on optimistic update with the code above:
export default createRefetchContainer(
BookItem,
{
book: graphql`
fragment BookItem_book on Book
#argumentDefinitions(
count: { type: "Int", defaultValue: 5 }
cursor: { type: "String", defaultValue: null }
) {
id
title
owner
createdAt
...BookComments_book
}
`
},
graphql`
query BookItemQuery($id: ID!, $count: Int, $cursor: String) {
book: node(id: $id) {
...BookItem_book #arguments(count: $count, cursor: $cursor)
}
}
`
);
and now the query for the comments component where it gets the book.comments.edges is undefined:
export default createPaginationContainer(
BookComments,
{
book: graphql`
fragment BookComments_book on Book
#argumentDefinitions(
count: { type: "Int", defaultValue: 3 }
cursor: { type: "String", defaultValue: null }
) {
id
title
comments(first: $count, after: $cursor)
#connection(key: "BookComments_comments", filters: []) {
__typename
edges {
node {
id
text
owner
createdAt
}
}
pageInfo {
startCursor
endCursor
hasPreviousPage
hasNextPage
}
}
}
`
},
{
direction: 'forward',
getConnectionFromProps: (props) => props.book && props.book.comments,
getFragmentVariables: (prevVars, totalCount) => ({
...prevVars,
count: totalCount
}),
getVariables: (props, { count, cursor }, _fragmentVariables) => ({
count,
cursor,
id: props.book.id
}),
query: graphql`
query BookCommentsQuery($id: ID!, $count: Int, $cursor: String) {
book: node(id: $id) {
...BookComments_book #arguments(count: $count, cursor: $cursor)
}
}
`
}
);
maybe this is an anti pattern? but i just wanted to have a optimistic effect for this
Some things are still not very clear to me about how those components and queries work, so I'll update this answer later. (I don't know if you want to return new book optimistically from node() query or add it to some list/connection of books)
Please check if I used correct type names (CommentConnection / CommentsConnection, etc)
optimisticUpdater: (store) => {
const userProxy = store.get(viewerId)
const owner = userProxy.getValue('username')
const commentsParams = { // must be same keys and values as in comments(first: $count, after: $cursor)
first: count,
after: cursor
}
// Create Book
const id = uuidv1();
const book = store.create(id, 'Book');
book.setValue(id, 'id');
book.setValue(bookTitle, 'title');
book.setValue(owner, 'owner');
book.setValue(Date.now(), 'createdAt');
// Create comments connection
const comments = store.create(uuidv1(), 'CommentConnection')
comments.setLinkedRecords([], 'edges')
// Create PageInfo
const pageInfo = store.create(uuidv1(), 'PageInfo')
pageInfo.setValue(null, 'endCursor')
pageInfo.setValue(false, 'hasNextPage')
pageInfo.setValue(false, 'hasPreviousPage')
pageInfo.setValue(null, 'startCursor')
// Link created records
comments.setLinkedRecord(pageInfo, 'pageInfo')
book.setLinkedRecord(comments, 'comments', commentsParams) // don't forget commentsParams with same values as are used in comments graphql query
// I'm not sure about this final part, because I don't really get how that app works, but if you want this book to show as optimistic response for `node(id: $id)`, you'll do something like this:
store.getRoot().setLinkedRecord(book, 'node', { id: id }) // same id as used in BookItemQuery
}
I have the following lines of code to add some variables to a local collection:
var data = {
name: '123',
brand: '123',
model: '123',
img: 'imgurl',
category: '123',
segment: 'Recreational',
pilotFstName: '123',
pilotLstName: '123',
insuranceNumber: '123',
insNumber2: '123',
extras: '123',
hasCamera: '123',
insuranceDate: '123'
};
var collectionName = 'Drones';
var options = {};
WL.JSONStore.get(collectionName)
.add(data, options)
.then(function(numberOfDocumentsAdded) {
//handle success
alert("Done");
})
.fail(function(errorObject) {
//handle failure
alert(errorObject);
});
This works fine working in a browser, but fails with an INVALID_SEARCH_FIELD error in any iOS physical device. This is the full error stack in Xcode.
[JSONStoreCollection findWithQueryParts:andOptions:error:] in
JSONStoreCollection.m:603 :: Error: JSON_STORE_INVALID_SEARCH_FIELD,
code: 22, collection name: Drones, accessor username: jsonstore,
currentQuery: (null), JSONStoreQueryOptions: [JSONStoreQueryOptions:
sort=( { identifier = desc; } ) filter=(null), limit=1, offset=(null)]
My Collections.js :
function getCollections(){
return {
Account : {
searchFields: {
userName:"string",
password:"string",
frstName:"string",
lstName:"string",
mail:"string"
}
},
Drones : {
searchFields: {
name:"string",
brand:"string",
model:"string",
img:"string",
category:"string",
segment:"string",
pilotFstName:"string",
pilotLstName:"string",
insuranceNumber:"string",
insNumber2:"string",
extras:"string",
hasCamera:"string",
insuranceDate:"string"
}
},
Historial : {
searchFields: {
name:"string",
date:"string",
posXStart:"string",
PosYStart:"string",
PosXFinish:"string",
PosYFinish:"string"
}
}
};
};
(function () {
WL.JSONStore.init(getCollections(), {
// password : 'PleaseChangeThisPassword'
})
.then(function () {
WL.Logger.debug(['All collections were loaded successfully'].join('\n'));
})
.fail(function (errObj) {
WL.Logger.ctx({pretty: true}).error(errObj);
});
}());
I had to create the collection since you did not mention it in your code snippet.
I also had to first initialize the JSONStore.
The following code works for me both in browser and iOS Simulator (that pretty much means also on a physical device in most cases such as this one):
main.js:
var collectionName = 'myCollectionObject';
var collections = {
myCollectionObject : {
searchFields : {
name: 'string',
brand: 'string',
model: 'string',
img: 'string',
category: 'string',
segment: 'string',
pilotFstName: 'string',
pilotLstName: 'string',
insuranceNumber: 'string',
insNumber2: 'string',
extras: 'string',
hasCamera: 'string',
insuranceDate: 'string'
}
}
};
var data = [{
name: '123',
brand: '123',
model: '123',
img: 'imgurl',
category: '123',
segment: 'Recreational',
pilotFstName: '123',
pilotLstName: '123',
insuranceNumber: '123',
insNumber2: '123',
extras: '123',
hasCamera: '123',
insuranceDate: '123'
}];
var options = {};
var addOptions = { };
function wlCommonInit(){
WL.JSONStore.init(collections, options)
.then(function () {
return WL.JSONStore.get(collectionName).add(data, addOptions);
})
.then(function (numberOfDocumentsAdded) {
alert ("success: " + numberOfDocumentsAdded);
})
.fail(function (errorObject) {
alert ("failure: " + errorObject);
});
}