graphql with prisma use where in update func with relation field - postgresql

schema.prisma file I need help how can I update a post based on 2 conditions? The user id that I get from userId which belongs to user tab and post id which belongs to on other post table
updatePost: async (parant, args, context) => {
const { userId } = context;
const postExist = await context.prisma.post.findUnique({
where: {
id: args.id,
author:{
id: userId
}
}
})
if (!postExist) {
throw new Error ('post not found')
}
const updatedPost = await context.prisma.post.update({
data:{
...args.data
}
})
return updatedPost
},
error code:
"message": "\nInvalid `context.prisma.post.findUnique()` invocation
Argument where of type PostWhereUniqueInput needs exactly one argument, but you provided id and author. Please choose one. Available args: ",
"type PostWhereUniqueInput {",
" id?: Int",
"Unknown arg `author` in where.author for type PostWhereUniqueInput. Available args:",
"",
"type PostWhereUniqueInput {",
" id?: Int",
"}",

You should use context.prisma.post.findFirst()
context.prisma.post.findUnique will only accept fields with #unique field in your schema file. In this case, that is only your Post id.

Related

prisma query existing item for failed unique key constraint

I have a form where clients can register for contests. They submit their name and email address, and the registration gets stored in a table in the database which has a composite primary key of the contestId and the clientId.
If someone submits the form a second time (i.e., tries to register for the same contest a second time), Prisma throws a failed constraint error. That's fine, and even expected, but I'd like to return the existing entry, and am having a hard time constructing the query.
Schema:
model Client {
id Int #id #default(autoincrement())
email String #unique
first String?
last String?
registrations Registration[]
}
model Contest {
id Int #id #default(autoincrement())
name String #unique
registrations Registration[]
}
model Registration {
contest Contest #relation(fields: [contestId], references: [id])
contestId Int
contestant Client #relation(fields: [clientId], references: [id])
clientId Int
points Int
##id ([contestId, clientId])
}
Registration:
try {
const registration = await prisma.registration.create({
data: {
points: 1,
contest: {
connectOrCreate: {
where: {
name: contest,
},
create: {
name: contest,
}
}
},
contestant: {
connectOrCreate: {
where: {
email: email,
},
create: {
first: first,
last: last,
email: email,
},
},
},
}
});
return res.status(201).send({ ...registration});
}
For new registrants that works, but if the registration already exists, I end up in the catch block. I assume this is the right way to do this — as opposed to, say, querying for existence first, because that seems expensive given that it's likely to be very rare that someone accidentally tries to re-register — but if this isn't a best practice on how to handle things, I'm open to other suggestions.
In the catch block, I then need to find the existing entry, but neither of the two things I've tried have worked:
catch (error) {
// entry already exists
if ('P2002' === error.code) {
const registration = await prisma.registration.findUnique({
where: {
contest: {
is: {
name: contest,
},
},
contestant: {
is: {
email: email,
},
},
},
})
return res.status(200).send(...registration);
}
return res.status(500).end(`Register for contest errorr: ${error.message}`);
}
complains Argument where of type RegistrationWhereUniqueInput needs exactly one argument, but you provided contest and contestant.
And
const registration = await prisma.registration.findUnique({
where: {
contestId_clientId: {
contest: {
is: {
name: contest,
},
},
contestant: {
is: {
email: email,
},
},
},
},
})
complains
Unknown arg `contest` in where.contestId_clientId.contest for type RegistrationContestIdClientIdCompoundUniqueInput. Did you mean `contestId`?
Unknown arg `contestant` in where.contestId_clientId.contestant for type RegistrationContestIdClientIdCompoundUniqueInput. Did you mean `contestId`?
Argument contestId for where.contestId_clientId.contestId is missing.
Argument clientId for where.contestId_clientId.clientId is missing.
I feel like this latter approach of using Prisma's auto-generated contestId_clientId is directionally right, but how do I construct the Prisma query to find it, starting from having the contest name and client email?
Instead of finding a unique record from the registration model, you can do a query something like this to find the Contest and Client details. The only valid argument for findUnique in the registration model would be: contestId_clientId and you can only pass contestId and clientId in them, other values are not allowed.
const registration = await prisma.client.findUnique({
where: {
email: 'email',
},
include: {
registrations: {
select: {
contest: contest,
points: true,
},
},
},
});

Postman GET request in order to retrieve a Mongodb entry by _id

I'm trying to build a postman GET request in order to retrieve an entry that I have in a MongoDB by using the unique id generated in the database.
To be more precise, I am interested in writing a GET request to retrieve for example the next entry :
{
"id": "61a51cacdfb9ea1bd9395874",
"Name": "asdsd",
"Code": "asdca",
"Weight": 23,
"Price": 23,
"Color": "sfd",
"isDeleted": false
}
Does anyone have an idea how to include that id in the GET request in order to retrieve the prodcut from above?
Thanks!
EDIT :
#J.F thank you for the kind response and information provided, but unforunately, it still does not work :(.
Those are the products that I have right now, and I tried to GET the one with id = 61a51cacdfb9ea1bd9395874
And this is the response I got :
Also, this is the logic that I implemented for the GET request :
filename : product.service.ts
async getSingleProduct(productId: string) {
const product = await this.findProduct(productId);
return {
id: product.id,
Name: product.Name,
Code: product.Code,
Weight: product.Weight,
Price: product.Price,
Color: product.Price,
isDeleted: product.isDeleted };
}
private async findProduct(id: string): Promise<Product> {
let product;
try {
const product = await this.productModel.findById(id)
} catch (error) {
throw new NotFoundException('Product not found');
}
if (!product) {
throw new NotFoundException('Product not found');
}
return product;
}
filename : product.controller.ts
#Get(':id')
getProduct(#Param('id') prodId: string) {
return this.productsService.getSingleProduct(prodId)
}
EDIT2 :
#Controller('produse')
export class ProductsController {
constructor(private readonly productsService: ProductsService) {}
#Post()
async addProduct(
#Body('Name') prodName: string,
#Body('Code') prodCode: string,
#Body('Weight') prodWeight: number,
#Body('Price') prodPrice: number,
#Body('Color') prodColor: string,
#Body('isDeleted') prodIsDeleted: boolean,
) {
const generatedId = await this.productsService.createProduct(
prodName,
prodCode,
prodWeight,
prodPrice,
prodColor,
prodIsDeleted
);
return { id: generatedId };
To implement a RESTful API your endpoints has to be like this:
Verb
Path
GET
/resource
GET
/resource/:id
POST
/resource
PUT
/resource/:id
DELETE
/resource/:id
The path you want is GET /resource/:id
The id is used into route because is unique and identify a resource.
So your path can be something like:
http://localhost:8080/v1/resource/61a51cacdfb9ea1bd9395874

Unknown argument error when creating record

I'm encountering some interesting behaviour when using Prisma ORM. It is related to Prisma's generated types, and I've been skimming the docs trying to find out more, but there doesn't seem to be much info about generated types in there (please correct me if I'm mistaken). Here's the behaviour:
Say I have a model with two 1-1 relations (Profile in the example below):
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int #id #default(autoincrement())
name String
profile Profile?
}
model Profile {
id Int #id #default(autoincrement())
name String
userId Int?
user User? #relation(fields: [userId], references: [id])
photoId Int?
photo Photo? #relation(fields: [photoId], references: [id])
}
model Photo {
id Int #id #default(autoincrement())
url String
profile Profile?
}
The following code works when creating a new profile:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
user: { connect: { id: user.id } },
photo: { create: { url: "http://example.com/img" } },
},
});
... but this fails with an error:
const user = await prisma.user.create({ data: { name: "TestUser" } });
const profile = await prisma.profile.create({
data: {
name: "TestProfile",
userId: user.id,
photo: { create: { url: "http://example.com/img" } },
},
});
The error is:
Unknown arg userId in data.userId for type ProfileCreateInput. Did you mean user? Available args:
type ProfileCreateInput {
  name: String
  user?: UserCreateNestedOneWithoutProfileInput
  photo?: PhotoCreateNestedOneWithoutProfileInput
}
Why is the second create-profile code invalid?
Prisma essentially generates two type definitions for a create query. This is implemented with a XOR type, which ensures that only one definition out of two is fully specified and passed to the query:
export type ProfileCreateArgs = {
/* ... */
data: XOR<ProfileCreateInput, ProfileUncheckedCreateInput>;
}
The definitions are called checked and unchecked, the former using nested fields and the latter using raw ids:
export type ProfileCreateInput = {
id?: number;
/* ... */
user?: UserCreateNestedOneWithoutProfileInput;
photo?: PhotoCreateNestedOneWithoutProfileInput;
}
export type ProfileUncheckedCreateInput = {
id?: number;
/* ... */
userId?: number;
photoId?: number;
}
Which basically means that you either provide all references as connect, create etc. relations (checked) or as raw ids (unchecked). You cannot mix the styles, this is not supported.

How to implement a node query resolver with apollo / graphql

I am working on implementing a node interface for graphql -- a pretty standard design pattern.
Looking for guidance on the best way to implement a node query resolver for graphql
node(id ID!): Node
The main thing that I am struggling with is how to encode/decode the ID the typename so that we can find the right table/collection to query from.
Currently I am using postgreSQL uuid strategy with pgcrytpo to generate ids.
Where is the right seam in the application to do this?:
could be done in the primary key generation at the database
could be done at the graphql seam (using a visitor pattern maybe)
And once the best seam is picked:
how/where do you encode/decode?
Note my stack is:
ApolloClient/Server (from graphql-yoga)
node
TypeORM
PostgreSQL
The id exposed to the client (the global object id) is not persisted on the backend -- the encoding and decoding should be done by the GraphQL server itself. Here's a rough example based on how relay does it:
import Foo from '../../models/Foo'
function encode (id, __typename) {
return Buffer.from(`${id}:${__typename}`, 'utf8').toString('base64');
}
function decode (objectId) {
const decoded = Buffer.from(objectId, 'base64').toString('utf8')
const parts = decoded.split(':')
return {
id: parts[0],
__typename: parts[1],
}
}
const typeDefs = `
type Query {
node(id: ID!): Node
}
type Foo implements Node {
id: ID!
foo: String
}
interface Node {
id: ID!
}
`;
// Just in case model name and typename do not always match
const modelsByTypename = {
Foo,
}
const resolvers = {
Query: {
node: async (root, args, context) => {
const { __typename, id } = decode(args.id)
const Model = modelsByTypename[__typename]
const node = await Model.getById(id)
return {
...node,
__typename,
};
},
},
Foo: {
id: (obj) => encode(obj.id, 'Foo')
}
};
Note: by returning the __typename, we're letting GraphQL's default resolveType behavior figure out which type the interface is returning, so there's no need to provide a resolver for __resolveType.
Edit: to apply the id logic to multiple types:
function addIDResolvers (resolvers, types) {
for (const type of types) {
if (!resolvers[type]) {
resolvers[type] = {}
}
resolvers[type].id = encode(obj.id, type)
}
}
addIDResolvers(resolvers, ['Foo', 'Bar', 'Qux'])
#Jonathan I can share an implementation that I have and you see what you think. This is using graphql-js, MongoDB and relay on the client.
/**
* Given a function to map from an ID to an underlying object, and a function
* to map from an underlying object to the concrete GraphQLObjectType it
* corresponds to, constructs a `Node` interface that objects can implement,
* and a field config for a `node` root field.
*
* If the typeResolver is omitted, object resolution on the interface will be
* handled with the `isTypeOf` method on object types, as with any GraphQL
* interface without a provided `resolveType` method.
*/
export function nodeDefinitions<TContext>(
idFetcher: (id: string, context: TContext, info: GraphQLResolveInfo) => any,
typeResolver?: ?GraphQLTypeResolver<*, TContext>,
): GraphQLNodeDefinitions<TContext> {
const nodeInterface = new GraphQLInterfaceType({
name: 'Node',
description: 'An object with an ID',
fields: () => ({
id: {
type: new GraphQLNonNull(GraphQLID),
description: 'The id of the object.',
},
}),
resolveType: typeResolver,
});
const nodeField = {
name: 'node',
description: 'Fetches an object given its ID',
type: nodeInterface,
args: {
id: {
type: GraphQLID,
description: 'The ID of an object',
},
},
resolve: (obj, { id }, context, info) => (id ? idFetcher(id, context, info) : null),
};
const nodesField = {
name: 'nodes',
description: 'Fetches objects given their IDs',
type: new GraphQLNonNull(new GraphQLList(nodeInterface)),
args: {
ids: {
type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))),
description: 'The IDs of objects',
},
},
resolve: (obj, { ids }, context, info) => Promise.all(ids.map(id => Promise.resolve(idFetcher(id, context, info)))),
};
return { nodeInterface, nodeField, nodesField };
}
Then:
import { nodeDefinitions } from './node';
const { nodeField, nodesField, nodeInterface } = nodeDefinitions(
// A method that maps from a global id to an object
async (globalId, context) => {
const { id, type } = fromGlobalId(globalId);
if (type === 'User') {
return UserLoader.load(context, id);
}
....
...
...
// it should not get here
return null;
},
// A method that maps from an object to a type
obj => {
if (obj instanceof User) {
return UserType;
}
....
....
// it should not get here
return null;
},
);
The load method resolves the actual object. This part you would have work more specifically with your DB and etc...
If it's not clear, you can ask! Hope it helps :)

Passing user input into GQL Queries

I am currently trying to figure out how to implement a search function by using the text data the user submits through a form as a variable argument in the query that returns an array of the results. The results from the AssetQueryInit query render just fine. (AssetQueryInit just renders the results of a hardcoded query)
I am a newbie and any help would be appreciated!
GraphQL Server
const typeDefs = `
type Asset {
_id: ID
assetName: String
headline: String
language: String
sentimentNegative: Float
sentimentNeutral: Float
sentimentPositive: Float
firstCreated: String
}
type Query {
asset(assetName: String!): Asset
assetAll(assetName: String!): [Asset]
}
`;
const resolvers = {
Query: {
asset: (_,{assetName})=> Asset.findOne({assetName : assetName }),
assetAll: (_,{assetName}) => Asset.find({assetName: assetName})
}
};
const server = new GraphQLServer({ typeDefs, resolvers });
React
const AssetAllQuery = gql`
{
assetAll(assetName: $text){
_id
assetName
headline
firstCreated
sentimentNegative
sentimentPositive
sentimentNeutral
}
}
`;
class App extends Component {
assetAll = async (text) =>{
await this.props.assetAll({
variables:{
assetName: text
},
update:(store,{data: {assetAll}}) => {
const data = store.readQuery({query: AssetAllQuery});
data.assetAll.unshift(assetAll);
}
});
};
render() {
const {
data : { loading, assetAll }
} = this.props;
console.log("test");
if(loading){
return null;
}
return (
<div className="App">
<Form submit ={this.getAllAssets}/>
{assetAll.map(Asset => <div key={Asset._id}>{Asset.headline} | {Asset.sentimentNeutral > Asset.sentimentPositive ? (Asset.sentimentNeutral > Asset.sentimentNegative ? "Neutral" : "Sell") : "Buy"} </div>)}
</div>
);
}
};
export default compose(
graphql(AssetAllQuery, {name: "assetAll"}),
graphql(AssetQueryInit)
)(App);
Output
[GraphQL error]: Message: Variable "$text" is not defined., Location: [object Object],[object Object], Path: undefined bundle.js:837:32
[Network error]: Error: Response not successful: Received status code 400
In the query, any $variables you use have to essentially be "declared" as input parameters to the whole thing:
const AssetAllQuery = gql`
AssetAll($assetName: String!) {
assetAll(assetName: $assetName){
...
}
}
`;
The top-level operation name doesn't really matter; the $variable has its own declared type and must match what gets used inside the body of the query; and when you make the query, the names of the variables: there have to match the names of the declared query parameters.