I have a Category model inside my prisma schema that has a field called parent:
model Category {
id Int #default(autoincrement()) #id
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
title String #unique
parent Boolean
parentId Int?
stores Storefront[]
products Product[]
}
How can I ensure there is always at least 1 Category where the parent is true? And if not, send back an error. Do I have to do this manually inside a mutation?
Example, updateCategory mutation:
export default async function updateCategory({ where, data }: UpdateCategoryInput, ctx: Ctx) {
const { title, parent, parentId } = CategoryInput.parse(data)
const category = await db.category.update({
where,
data: {
title,
parent,
parentId: parent ? null : parentId,
},
})
return category
}
It seems this is business logic you would have to implement in your resolver before update, I don't think this can be achieved by setting constraints in the DB or on a Prisma-level.
Here's what I would suggest to enforce this constraint in your application:
export default async function updateCategory({ where, data }: UpdateCategoryInput, ctx: Ctx) {
const { id, title, parent, parentId } = CategoryInput.parse(data)
// Check how many categories there are with `parent` set to `true`
const categories = await db.category.findMany({
where: { parent: true }
})
if (categories.length === 1 && categories[0].id === id && !parent) {
// if all these conditions are true, you are about to set the last
// category with `parent` equals `true` to `false` which must not happen
throw new Error(`This mutation would set the last parent to false`)
}
const category = await db.category.update({
where: {
id: id
},
data: {
title,
parent,
parentId: parent ? null : parentId,
},
})
return category
}
Note that you would have to add this check to every part in your application where parent is potentially set to false.
Let me know if that helps or if you have any further questions :)
Related
I have 2 tables:
model Collection {
id String #id #default(uuid()) #db.Uuid/
floorPrices CollectionFloorPrice[]
}
model CollectionFloorPrice {
id String #id #default(uuid()) #db.Uuid
collection Collection #relation(fields: [collectionId], references: [id])
collectionId String #db.Uuid
}
How do I query collections that only have rows present in CollectionFloorPrice? In SQL it would be a simple JOIN.
This isn't working:
return await this.prisma.collection.findMany({
where: {
floorPrices: {
exists: true,
},
},
});
Prisma's relation filters for a model named CollectionFloorPrice are:
export type CollectionFloorPriceFilter = {
every?: CollectionFloorPriceWhereInput | null
some?: CollectionFloorPriceWhereInput | null
none?: CollectionFloorPriceWhereInput | null
}
To get only Collections that have at least one CollectionFloorPrice, you should use some (instead of exists) and specify a condition that always return true for any related record that exists.
And if you want to your query includes related CollectionFloorPrices you must specify it in include property.
return await this.prisma.collection.findMany({
where: {
floorPrices: {
some: {
id: { not: "" } // It always should be true.
},
},
},
// if you want to include related floor prices in returned object:
include: {
floorPrices: true,
},
});
prisma.collection.findMany({
where: { floorPrices: { some: {} } }
})
I have a list of options
const values = [
{ firstName: 'John',
id: 'text'
}.
{
userId: 123,
id: 'number'
}
];
The logic is that the user selects one of the values (firstName) and an input field will be generated of the type 'string'. If the user selects userId, then a text input will be generated of the type 'int'. I need to create a logic where the user will be able to select another option from the drop-down and a text field should be generated. For example, if the option selected is True or another number and it should provide a boolean or an integer text field. I am also using immutability helper to update the type, but I did not clearly understand much from the official docs. Any idea on how to implement this with react?
Here is my immutability helper function
const setValueType = (type: string, index: number) => {
if (index < 0 || index >= data.values.length) {
return;
}
setValueData(update(data, {
values: {
[index]: {
type: { $set: type }
}
}
}));
};
const setPlaceholder = (placeHolder: any, index: number) => {
if (index < 0 || index >= data.values.length) {
return;
}
setValueData(update(data, {
values: {
[index]: {
placeHolder: { $set: placeHolder }
}
}
}));
}
I am using 'setValueType' function to update the type, and 'setPlaceHolder' to set a display text, I have to come up with a way where I will be adding new options to the drop-down, but they should render conditionally based on the type.
I am thinking that I have to pass an if-else condition in the 'setValueType' function, but I am not sure on implementing it.
I have the following REST endpoints:
/orders/{id}
returns {
orderId,
orderItem,
customerId
}
/customers/{id}
returns {
customerId,
firstName,
lastName
}
I am limited by these two endpoints, which are going to be wrapped in my graphql schema.
I would like the following Schema:
type Order {
orderId: ID!,
orderItem: String,
customer: Customer
}
type Customer{
customerId: ID!
firstName: String!
lastName: String!
}
type Query {
getOrder(id: String!): Order,
getCustomer(id: String!): Customer
}
I'm wondering if it is possible to have GraphQL resolve the Customer object in the Order type? I understand that you cannot pass the result of a query into the parameter of another.
I have considered the resolver of getOrder be:
const getOrderResolver = axios.get(`/orders/${id}`)
.then((ordersRes) => {
let customerId;
if(ordersRes.data.customerId !== null) {
customerId = ordersRes.data.customerId
axios.get(`/customers/${customerId}`)
.then((customerRes) => ({
return {
orderId: ordersRes.data.orderId
orderItem: ordersRes.data.orderItem
customer: {
customerId: customerRes.data.customerId
firstName: customerRes.data.firstName
lastName: customerRes.data.lastName
}
}
})
} else {
return {
orderId: ordersRes.data.orderId
orderItem: ordersRes.data.orderItem
customer: null
}
}
})
})
getCustomer resolver
const getCustomerResolver = axios.get(`/customers/${customerId}`)
.then((customerRes) => ({
return {
customerId: customerRes.data.customerId
firstName: customerRes.data.firstName
lastName: customerRes.data.lastName
}
})
It seems with my solution, there will be the additional cost of always fetching the Customer type whether or not it is queried within the getOrder query. Is it possible to rewrite my GraphQL schema in a way that GraphQL would be able to resolve the Customer type only when queried?
The limitation of my given my ORDERS REST API only returns the CustomerId makes it difficult to resolve in getOrder, since the Customer API requires a customerId
There's two things to keep in mind here:
GraphQL won't resolve a field unless it's requested (i.e. your resolver will not be called unless the field is actually included in the request).
Each resolver has access to the value its "parent" field resolved to.
So your getOrder resolver can only worry about returning an order object:
const resolvers = {
Query: {
getOrder: (parent, args, context, info) => {
const response = await axios.get(`/orders/${args.id}`)
return response.data
},
...
},
...
}
Note that if the REST endpoint returns a response in the same "shape" as your field's type, there's no need to map the response data to your actual fields here -- just return response.data.
Now we can add a resolver for the customer field on our Order type:
const resolvers = {
Query: { ... },
Order: {
customer: (parent, args, context, info) => {
if (!parent.customerId) {
return null
}
const response = await axios.get('/customers/${parent.customerId}')
return response.data
},
},
...
}
Our parent field here will be getOrder (or any other field that has an Order type). The value of parent will be whatever value you returned in that field's resolver (or if you returned a Promise, whatever that Promise resolved to). So we can use parent.customerId for the call to the /customers endpoint and return the data from the response. And again, this resolver will only be called if the customer field is actually requested.
I presently have a page with a dynamically created form. I am having trouble understanding how to manipulate the state and GraphQL query to handle nested queries.
With my present implementation it does not seem to be able to create any new entries. I want to create 1 "target" with several sub "addr" tied to it in one mutation.
This is the state definitions:
state = {
name:'',
addr:[{
mobilepkg:'',
target_url:'',
target_ip: '',
idCars:[]
}],
category:'',
date: '',
location:''
}
Handler for Graph:
handleTarget = async e => {
e.preventDefault()
const { name,
target_url,
target_ip,category,
mobilepkg,date,location } = this.state
let idCars = this.state.idCars
let adras = this.state.addr
await this.props.createTargetMutation({
variables: {
data: {
name,
addr:{
create:
[{
target_url,
target_ip,
mobilepkg,
cars: {
connect: idCars
},
}]
},
date,
location,
category
}
}
})
this.props.history.replace('/targets')
}
}
My create mutation
const CREATE_DRAFT_MUTATION = gql`
mutation CreateTargetMutation($data: TargetCreateInput!) {
createTarget(data: $data) {
id
name
addr
category
}
}
`
GraphQL datamodel
type Target {
id: ID! #unique
name: String!
addr: [Addr!]!
category: String!
date:String!
location:String!
}
type Addr {
id: ID! #unique
target_url:String!
target_ip:String!
mobilepkg:String!
cars: [Car!]!
}
How do I put my ReactJS state which has a nested array into GraphQL?
PS:I am new to GraphQL and ReactJS.
EDIT: In playground im able to create my items but its not working in my actual application.
mutation CreateTargetMutation($data: TargetCreateInput!) {
createTarget(data: $data) {
id
name
addr{
target_ip
target_url
mobilepkg
cars{
id
}
}
category
date
location
}
}
{
"data": {
"name":"testerquery",
"addr": {
"create": {
"target_ip":"123",
"target_url":"123",
"mobilepkg":"asd",
"cars":{"connect":{"id":"cjs3yd83u004a0781jffzaqqr"}}
}
},
"category":"simple",
"date":"2019-03-12",
"location":"kl"
}
}
Bro, you are one the right path. You just need to iterate your values in order to solve this problem. Once you iterate through the values you simply need to make a call to the new array which contains everything and it will work. As your values are nested you will need to add "{}" to your car variable and within that contain your connect since you wish to create new "addr" whilst connecting to existing "car".
let create = []
for(let i=0; i < this.state.addr.length; i++){
create.push({
'mobilepkg':this.state.addr[i].mobilepkg,
'target_url':this.state.addr[i].target_url,
'target_ip':this.state.addr[i].target_ip,
'cars': {
'connect': this.state.addr[i].cars}
})
}
await this.props.createTargetMutation({
variables: {
data: {
name,
addr: {
create
},
category,
date,
location
}
}
})
this.props.history.replace('/targets')
}
}
Your values should now successfully pass into GraphQL and create targets with many "addr" whilst connecting to many "car"
I am trying to create a reusable table component where I can only pass down as props the data and the filters and it will already handle most of the work for me. However, right now, I am hardcoding the select variables into the Table state and if I am to create another table with different filters I would have to create another component for that. And that's not viable in anyway.
right now I have a master component that hold the data and filter and passes down to Table component, like that:
class Master extends React.Component {
listHotels: [
createObject('BlueTree','05-02-2015','ativo', 'Vinhedo', 'São Paulo'),
createObject('Inner','07-08-2016','inativo', 'Belo Horizonte', 'Minas Gerais'),
createObject('Teste','05-02-2017','ativo', 'Teresina', 'Piauí'),
createObject('hello','05-02-2015','ativo', 'Osasco', 'São Paulo'),
createObject('Inner','07-08-2016','inativo', 'Lavras', 'Minas Gerais'),
createObject('Teste','05-02-2017','inativo', 'Barras', 'Piauí'),
createObject('xiaomi','05-02-2015','inativo', 'Indaiatuba', 'São Paulo'),
createObject('Inner','07-08-2016','ativo', 'Pedrinhas', 'Minas Gerais'),
createObject('Teste','05-02-2017','ativo', 'Esperantina', 'Piauí'),
].sort((a, b) => (a.id < b.id ? -1 : 1)),
selectFilter: [
{ id: 1, type: 'Name', options},
{ id: 2, type: 'Data', options},
{ id: 3, type: 'Cidade', options},
{ id: 4, type: 'Estado', options},
{ id: 5, type: 'PMS', options},
],
}
and then I call the table component on render function
<Table listHotels={listHotels} toggleList={this.toggleList} selectFilter={selectFilter}></Table>
On the Table component I have the state to keep track of the filter select options as you can see below(status, pms, state...):
state = {
page: 0,
rowsPerPage: 5,
query: '',
filter: [],
status: null,
pms: null,
state: null,
city: null,
name: null,
isLoading: false
};
And whenever the option is selected I create an API call to hit the back end and respond with some data to fill the state's filter property.
componentDidUpdate(prevProps, prevState){
const { status, pms, state, city, page, rowsPerPage } = this.state;
const filterOptions = [];
if (status !== null){
filterOptions.push({name: "status", value: status});
}
if (pms !== null){
filterOptions.push({name: "pms", value: pms});
}
if (state !== null){
filterOptions.push({name: "estado", value: state});
}
if (city !== null){
filterOptions.push({name: "cidade", value: city});
}
if(filterOptions.length !== 0){
const url = createAPICall(filterOptions, page, rowsPerPage);
//call api and set it to the filter property
}
However, the way the component is right now is not reusable. If I want to create another table with other filters, I would have to change the state to hold the filter variables and also to create the API url.
How can I make this more dynamic?
assuming you want to make the filter types more flexible. part of the issue is you manually list them out in state and table. start by bundling your filter types into an object to pass through from master (or extract them from selectFilter).
When you set state, set filters: {...parse filters here...}.
next in componentDidUpdate, you don't want to be manually listing each if statement, you want to iterate over your filters obj definition, adding an if per obj key in each loop. When values are updated in your form/table, push the key value in your state object, not the individual values like you have now.
i.e.
const { status, pms, state, city, page, rowsPerPage } = this.state;
would look more like
const { filters, page, rowsPerPage } = this.state;