I have a question about MongoDB ISODate type and GraphQL. I need to declare a mutation in my gql schema that allows to add a document in my Mongo database.
This document has an ISODate property, but in my gql schema, I'am using a String :
mutation addSomething(data: SomeInput)
type SomeInput {
field1: String
field2: Int
created: String
}
My problem is that, in the new document, the created field is in String format (not ISODate), and I was expecting that. But I wonder how to do to make it insert an ISODate instead. Is there a "custom type" somewhere I could use instead a String ?
Thank you
PS: I'am using nodeJS and apollo libraries.
Edit 1 : Trying with the graphql-iso-date package
I have found this package https://www.npmjs.com/package/graphql-iso-date that adds 3 date custom types.
Here is my gql schema :
const { gql } = require('apollo-server');
const { GraphQLDateTime } = require('graphql-iso-date')
const typeDefs = gql`
scalar GraphQLDateTime
type Resto {
restaurant_id: ID!
borough: String
cuisine: String
name: String
address: Address
grades: [Grade]
}
type Address {
building: String
street: String
zipcode: String
coord: [Float]
}
type Grade {
date: GraphQLDateTime
grade: String
score: Int
}
input GradeInput {
date: GraphQLDateTime
grade: String
score: Int
}
extend type Query {
GetRestos: [Resto]
GetRestoById(id: ID!): Resto
}
extend type Mutation {
UpdateGradeById(grade: GradeInput!, id: ID!): Resto
RemoveGradeByIdAndDate(date: String, id: ID!): Resto
}
`
module.exports = typeDefs;
This is a test based on the sample restaurants dataset.
So, if I try to call the UpdateGradeById() function like this :
UpdateGradeById(grade:{date:"2020-08-25T08:00:00.000Z",grade:"D",score:15},id:"30075445"){...}
The document is updated but the date is always in String format (as you can see on the screenshot bellow) :
The date of the last grade in the list is recognized as a string (not as a date).
I can see an improvement though because before I was using the graphql-iso-date date fields were returned in timestamp format. Now they are returned as ISO string. But the insertion does not work as expected.
Ok, I missed to do something important in my previous example : resolvers.
So, if like me you want to manipulate MongoDB date type through GraphQL, you can use the graphql-iso-date package like this :
First, modify your schema by adding a new scalar :
const { gql } = require('apollo-server');
const typeDefs = gql`
scalar ISODate
type Resto {
restaurant_id: ID!
borough: String
cuisine: String
name: String
address: Address
grades: [Grade]
}
type Address {
building: String
street: String
zipcode: String
coord: [Float]
}
type Grade {
date: ISODate
grade: String
score: Int
}
input GradeInput {
date: ISODate
grade: String
score: Int
}
extend type Query {
GetRestos: [Resto]
GetRestoById(id: ID!): Resto
}
extend type Mutation {
UpdateGradeById(grade: GradeInput!, id: ID!): Resto
RemoveGradeByIdAndDate(date: ISODate!, id: ID!): Resto
}
`
module.exports = typeDefs;
(Here I choose to call my custom date scalar ISODate)
Then, you have to tell how to "resolve" this new ISODate scalar by modifying you resolvers file :
const { GraphQLDateTime } = require('graphql-iso-date')
module.exports = {
Query: {
GetRestos: (_, __, { dataSources }) =>
dataSources.RestoAPI.getRestos(),
GetRestoById: (_, {id}, { dataSources }) =>
dataSources.RestoAPI.getRestoById(id),
},
Mutation: {
UpdateGradeById: (_, {grade,id}, { dataSources }) =>
dataSources.RestoAPI.updateGradeById(grade,id),
RemoveGradeByIdAndDate: (_, {date,id}, { dataSources }) =>
dataSources.RestoAPI.removeGradeByIdAndDate(date,id),
},
ISODate: GraphQLDateTime
};
And that's it. Now, date properties in my mongodb documents are well recognized as Date type values.
Related
I'm working with Prisma for the first time and I'm not sure if I'm going about this in the right way.
Here's a snippet of my schema
model Economy {
id String #id #default(auto()) #map("_id") #db.ObjectId
shops Shop[]
}
type Shop {
id Int #default(0)
name String #default("")
items ShopItem[]
EconomyId String? #db.ObjectId
}
type ShopItem {
id Int #default(0)
enabled Boolean #default(true)
name String #default("")
price Int #default(0)
description String #default("")
commands ShopCommand[]
}
type ShopCommand {
command String #default("")
}
This does work of course and it's easy for me to destructure in JavaScript however inserting and updating is complicated.. here's an example of updating a "ShopItem"
const newItem = {
id: 2,
name: body.name,
price: parseInt(body.price),
description: body.description,
enabled: body.enabled == 'true',
commands: newCommands
}
const newEconomy = await prisma.economy.update({
where: {
guildId: guildId,
},
data: {
shops: {
updateMany: {
where: {
id: 0,
} ,
data: {
items : {
push: [newItem]
}
}
}
}
}
})
This is hard to read and I feel like I'm doing this all wrong.
I've looked into how other people go about the same thing but I haven't found much information. Should I even be using composite types, should each type be inside it's own collection instead?
It looks like Shop and ShopItem could be their own models. They look sufficiently complex and relational in nature to warrant a separate entity.
Composite types are best suited for smaller, structured pieces of data that might be repeated or shared between several other models while not being necessarily relational or unique in nature.
I have 2 collections:
profiles
dances
struct Profile {
id: String,
firstname: String,
dances: Vec<String>
}
struct DanceModel {
id: String,
name: String,
}
I have a find query which does a lookup on the dances collection.
let cursor = profile_collection.aggregate(pipeline, None).await.unwrap();
let results = cursor.try_collect().await.unwrap_or_else(|_| vec![]);
let profile = results.first().unwrap();
let result = bson::from_document::<ProfileResult>(profile.clone());
I created a new model like so:
struct ProfileResult {
id: String,
name: String,
dances: Vec<DanceModel>
}
As you can see, the id and name fields are being duplicated, and I have 2 models for the same data.
Is there any way to avoid duplication of attributes and data models?
It looks like you are trying to apply an object concept to a procedural technology.
To work around this issue, you can create an IdName struct and apply that struct to the model in a uid field.
struct IdName {
id: String,
name: String,
}
struct Profile {
id: String,
firstname: String,
dances: Vec<String>
}
struct DanceModel {
uid: IdName
}
struct ProfileResult {
uid: IdName
dances: Vec<DanceModel>
}
I have written a save function
let saveRecord(record: PostDataItem, coll: IMongoCollection<PostDataItem>, orderNumberToCheck: string) =
let filterDefinition = Builders<PostDataItem>.Filter.Eq((fun d -> d._id), record._id)
let update = Builders<PostDataItem>.Update.Set((fun f -> f.orderNumberToCheck),orderNumberToCheck)
let options = new UpdateOptions(IsUpsert=true)
let result = coll.UpdateOne(filterDefinition, update, options)
result
Unfortunately the result from MongoDB tells me that it found a match when trying to update, but it did not modify it ( matched=1, modified=0 ). The only unique thing about this is that the field "orderNumberToCheck" doesn't actually exist prior to this - but I assumed that upsert would take care of that. My type is below.
[<CLIMutable>]
type PostDataItem = {
_id: BsonObjectId
dateLodged: DateTime
productCode: string
productDescription: string
clientReference: string
manifestContract: string
client: string
quantity: string
unitPrice: string
gst: string
total: string
clientReference2: string
weight: string
reference1: string
ticketNumber: string
transactioncode: string
invoiceExplanationField1: string
invoiceExplanationField2: string
invoiceExplanationField3: string
invoiceExplanationField4: string
invoiceExplanationField5: string
invoiceExplanationField6: string
[<BsonDefaultValue(null)>]
orderNumberToCheck: string
[<BsonDefaultValue(null)>]
isUnique: Nullable<bool>
[<BsonDefaultValue(null)>]
expectedPrice: Nullable<decimal>
}
How can I query (an undefined) number of nested elements from my document?
I would like to get the list of items without having to go
menus {
id
items {
id
name
items {
id
name
............
}
}
}
}
I have a prisma schema (MongoDB) that looks like this
enum MenuType {
MAIN
HYPERLINK
}
type Menu {
id: ID! #id
type: MenuType!
name: String!
title: String
href: String
t_blank: Boolean
items: [Items]
}
type Items #embedded{
id: ID! #id
type: MenuType!
name: String!
title: String
href: String
t_blank: Boolean
items: [Items]
}
I would like to get all my items with something like this
{
menus {
id
items
}
}
Is it possible? Does my consumer need to keep looping through the reponse body?
I declare a model in ingredient.model.ts
export class Ingredient {
constructor(private name: string, public amount: number) {}
getName() { return this.name }
}
In ingredients.service.ts, if I get them in this way:
httpClient.get<Ingredient>(url).subscribe(
(igredient) => {
console.log(igredient.getName());
});
It gives errors in console, such as "no method getName in property igredient".
Also, whenever I try to declare a property type Category[] it fails, but Array seems working fine.
Edit:
I want to provide more info.
Given the Igredient model and the following JSON structure:
{
name: "Apple",
amount: "5",
created_at: "date",
}
The Igredient constructor isn't even invoked, therefore the GET payload won't be parsed.
You'll need to use a property, not a method. The returned object is really a json object, and there is no such thing as "getName()" method (despite your effort to add a type information). Try something like this:
export interface Ingredient {
strin: string,
amount: number,
created_at: string
}
httpClient.get<Ingredient>(url).subscribe(
(igredient) => {
console.log(igredient.amount);
});
EDIT: You need to provide a type information based on the expected json object. If the returned json object has attributes, strin, amount, and created_at, then you need to define a type that is compatible with the expected json object.
In angular 5, You can do this:
export interface Deserializable<T> {
deserialize(input: any): T;
}
export class Ingredient implments Deserializable<Ingredient>{
constructor(private name: string, public amount: number) {}
deserialize(input: any): Project {
Object.assign(this, input);
// do nested thing here -pop arrays of nested objects and create them
}
return this;
}
now in your service:
httpClient.get<Ingredient>(url).pipe(map(elem=>this.foo(elem)))
.subscribe((igredient) => {console.log(igredient.getName());
});
foo(ingredient:Ingrdient){
var i = new Ingridiant().desrialize(ingredient)
}
after the map you will have the Ingradient class, not the object.