Prisma GraphQL nested document - mongodb

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?

Related

GraphQL TypeDef & Resolver for array of ObjectIds (MongoDB)

I have a number of documents in my sites collection.
These documents contain a pages property, which is an Array of ObjectId's.
// documents in my 'sites' collection.
{
_id: ObjectId("5e607b2643e2640056466402")
title: "Some Title"
...
pages: [
ObjectId("5e57bcbd0166a10055c43bb7")
ObjectId("5e5ba2630166a10055c5c952")
...
]
}
I am using GraphQL to fetch these results using Apollo & Prisma and I want to return the actual pages as opposed to their individual IDs.
My schema.prisma looks like this
model pages {
id String #id #default(auto()) #map("_id") #db.ObjectId
title String?
uri String?
}
model sites {
id String #id #default(auto()) #map("_id") #db.ObjectId
pages String[] #db.ObjectId
title String?
url String?
}
My typeDefs.js file looks like this:
type pages {
id: ID!
title: String
uri: String
}
type sites {
id: ID!
pages: [String]
title: String
url: String
}
And my resolver looks like this:
allSites: () => prisma.sites.findMany(),
When I send the following GraphQL request:
query myQuery {
allSitesRecursive {
title
pages
}
}
I get this response:
{
"data": {
"allSites": [
{
"title": "Some Title",
"pages": [
"5dc9630226b1b2005952c4b5",
"5e5797020166a10055c41f94",
...
]
}
}
}
}
This is fine, but I want to extract the individual title and uri from each page in the pages array, as opposed to just having the reference ID.
Is this possible to achieve without reorganising my data?
If so, how?
EDIT:
I have changed my resolver to:
allSites: () => prisma.sites.findMany({ include: { pages: true } }),
But then I receive an error stating:
Invalid scalar field `pages` for include statement on model sites.
This model has no relations, so you can't use include with it.
Note, that include statements only accept relation fields.,
So then I change my schema.prisma to:
model pages {
id String #id #default(auto()) #map("_id") #db.ObjectId
title String?
uri String?
sites sites? #relation(fields: [sitesId], references: [id]) // gets added automatically on save
sitesId String? #db.ObjectId // gets added automatically on save
}
model sites {
id String #id #default(auto()) #map("_id") #db.ObjectId
pages pages[]
title String?
url String?
}
and my sites typeDefs to:
type sites {
id: ID!
pages: [pages]
title: String
url: String
}
But my response to the following GraphQL query:
query AllSites {
allSites {
pages {
title
uri
}
title
}
}
is:
{
"data": {
"allSites": [
{
"pages": [],
"title": "www.some-website.com"
},
...
}
}
}
The pages array is empty, which suggests that it is not able to fetch the pages by reference.
Am I doing something wrong?

Prisma and MongoDB Composite Types

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.

Avoid duplication when serializing/deserializing mongodb documents

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>
}

MongoDB, GraphQL and ISODate type

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.

How to use a slug in Vapor 3

How would I go about generating a slug for a SQLiteModel object, and using it in Leaf for Vapor 3? My goal is to provide a search engine friendly link to the detail page of my model (for example, /tag/swift/ vs. /tag/0/), rather than link using the id.
I found the Parameter protocol, which means my model has a uniqueSlug property. Unfortunately using it in Leaf #(model.uniqueSlug) doesn't work.
Here's my leaf code:
#for(tag in tags) {
<a class="tag" href="tag/#(tag.id)">#(tag.name)</a>
}
Here's my model:
final class Tag: SQLiteModel, Codable {
var id: Int?
var name: String
var description: String
init(id: Int? = nil, name: String, description: String) {
self.id = id
self.name = name
self.description = description
}
}
extension Tag: Migration { }
extension Tag: Content { }
extension Tag: Parameter { }
I also tried adding a computed property to the model:
var slug: String {
return "test"
}
That also doesn't work.