Graphql schema code to view user profiles - javascript

Am new to Graphql and actually following a tutorial. I am building a project in React Native and using AWS Amplify and Graphql for my backend. I needed a little change from the tutorial am following, I want users to be able to view user profile of other users in a their contact list just Instagram or Facebook.
In my schema.graphql I have the following code:
type User #model {
id: ID!
name: String!
imageUri: String
username: String!
email: String!
}
But I don't know the next code to write for user profile and the relationships for users to view other user user profiles.
I have been able to list contacts with the following code:
useEffect(() => {
const fetchUsers = async () => {
try {
const usersData = await API.graphql(
graphqlOperation(
listUsers
)
)
setUsers(usersData.data.listUsers.items);
} catch (e) {
console.log(e);
}
}
fetchUsers();
}, [])
Please I need guide on how to achieve viewing user profile when user is clicked on the contact list.

you have to add "auth" rule to your model
type User #model
#auth(
rules: [
#this is for logged-in user. cognito user is default for provider
# In order to prevent private users from creating, another rule must be set for creating
{ allow: private, operations: [read] }
# default provider is cognito
{ allow: private, provider: iam, operations: [read, create, update, delete] }
# Only Owner can update its own data
{ allow: owner, ownerField: "username", operations: [update] }
]
) {
id: ID!
name: String!
imageUri: String
username: String!
email: String!
}
In the above code, I defined two auth rules. One is for Cognito user, he can only read, another one for the "iam" user who has more privileges.

Related

next-auth with EmailProvider - is it possible to assign a new user to a role based on their email domain?

I'm using NextAuth and EmailProvider to handle log-in to a Next.js app, and new users are saved in a postgres database using Prisma. The schema includes a isAdmin field that defaults to false
I'd like to set that field to true for new users who sign in from a specific email domain but can't work out if that's possible - at the moment I need to go into the dB and set the flag manually once the user has signed up, which is obviously very much not ideal.
I'd very much appreciate any pointers, or even confirmation that what I'm trying to do is impossible!
ETA: I now think I may need to add either an Event or a Callback to the NextAuth function, but the documentation focuses on redirecting users rather than on user creation, so I'm still not sure if what I'm trying to do is even possible (https://next-auth.js.org/configuration/events).
Below is my pages>api>auth>[...nextauth].js code.
import NextAuth from "next-auth"
import EmailProvider from "next-auth/providers/email"
import { PrismaAdapter } from "#next-auth/prisma-adapter"
import prisma from "lib/prisma"
let data
if (process.env.NODE_ENV === "development") {
data = {
server: process.env.EMAIL_SERVER,
from: process.env.EMAIL_FROM,
}
} else {
data = {
server: {
host: process.env.EMAIL_SERVER_HOST,
port: process.env.EMAIL_SERVER_PORT,
auth: {
user: process.env.EMAIL_SERVER_USER,
pass: process.env.EMAIL_SERVER_PASSWORD,
},
},
from: process.env.EMAIL_FROM,
}
}
export default NextAuth({
providers: [EmailProvider(data)],
database: process.env.DATABASE_URL,
secret: process.env.SECRET,
session: {
jwt: true,
maxAge: 30 * 24 * 60 * 60, // 30 days
},
debug: true,
adapter: PrismaAdapter(prisma),
callbacks: {
session: async ({ session, user }) => {
session.user.id = user.id
session.user.isAdmin = user.isAdmin
return Promise.resolve(session)
},
},
})
and my Prisma schema
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String #id #default(cuid())
name String?
email String? #unique
emailVerified DateTime?
createdAt DateTime #default(now())
updatedAt DateTime #updatedAt
isAdmin Boolean #default(false)
accounts Account[]
sessions Session[]
}
model VerificationToken {
identifier String
token String #unique
expires DateTime
##unique([identifier, token])
}
model Account {
id String #id #default(cuid())
userId String
type String
provider String
providerAccountId String
refresh_token String? #db.Text
access_token String? #db.Text
expires_at Int?
token_type String?
scope String?
id_token String? #db.Text
session_state String?
oauth_token_secret String?
oauth_token String?
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
##unique([provider, providerAccountId])
}
model Session {
id String #id #default(cuid())
sessionToken String #unique
userId String
expires DateTime
user User #relation(fields: [userId], references: [id], onDelete: Cascade)
}
I have solved my own problem and the answer is "no ...but". If it is possible to hijack the next-auth user creation process, I haven't found out how - but I now suspect that it would involve forking the next-auth code and would all round be a Really Bad Idea anyway. So "no, you can't assign a role to a user at the point of creation"
However, as I mentioned in my edit to my question, next-auth has the concept of Events, which allow you to execute your own code in response to next-auth life-cycle occurrences e.g. the signIn event is triggered every time a successful log in occurs.
So I used the signIn event, which receives the user object and an isNewUser boolean as params. I check if the user is newly-created and if so, do they have the specified email domain? If yes, then I update the database after the user has been created.
Since this is all done during sign in, it looks like a single-step process to the user.
In detail:
(1) add the required email domain to the .env file:
ADMIN_EMAIL_DOMAIN="whitelist.domain.com"
(2) add an event to the NextAuth function in [...nextauth].js:
//...code as above
import { updateUserToAdmin } from "lib/data.js"
//...code as above
export default NextAuth({
//...code as above
events: {
signIn: async ({ user, isNewUser }) => {
if (isNewUser) {
const userEmail = user.email
const isAdminEmail =
userEmail.split("#")[1] === process.env.ADMIN_EMAIL_DOMAIN
isAdminEmail
? await updateUserToAdmin(user.id, prisma, isAdminEmail)
: console.log(`non-Admin domain`)
}
},
},
})
(3) add the updateUserToAdmin query to lib>data.js:
return await prisma.user.update({
where: {
id: userId,
},
data: {
isAdmin: isAdminEmail,
},
})
}
I hope someone else finds this helpful. Do feel free to comment if you want to suggest any improvements.

Disconnecting Many-to-Many Relationships in Prisma + MySQL

I am so completely lost. I have an explicit many to many relation: Users can have multiple Lists, but lists can be owned by multiple users:
model List {
id String #id #default(cuid())
title String
users UsersOnLists[]
}
model User {
id String #id #default(cuid())
name String
lists UsersOnLists[]
}
model UsersOnLists {
id String #id #default(cuid())
order Int
user DictItem? #relation(fields: [userId], references: [id])
userId String?
list List? #relation(fields: [ListId], references: [id])
listId String?
}
Now I'd like to connect a list to a user:
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
create: [{
order: 123,
user: {
connect: {
id: "abcd-123",
}
}
}],
}
}
});
This works.
However, I don't know how to go about disconnecting many-to-many relations in prisma? Say I want to disconnect the user again from the list? How would I do this?
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
disconnect: [{
user: {
disconnect: {
id: "abcd-123",
}
}
}],
}
}
});
This doesn't work.
I also can't find much in the prisma docs about disconnecting. Any ideas?
I guess I could jus delete the row from the Relations-Table, but this doesn't feel as clean and I guess I would still have the old ids in the user & list tables? I would prefer using disconnect, if this is the recommended method for that.
Are you getting a specific error? If you are using a code editor/IDE with TypeScript hinting, it should be giving you a specific error(s) about what's going on. If not that, then the command line should be giving you errors when you attempt to run an operation.
Docs: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#disconnect-a-related-record
The "disconnect" operation cannot disconnect deeply-nested relations. It only disconnects documents directly connected to the model in question. In your situation, you can only disconnect a UserOnList from a List, but you cannot also disconnect User from UserOnList in the same operation.
prisma.list.update({
where: {
id: input.id
},
data: {
users: {
disconnect: [{
id: "ID_OF_UsersInList_MODEL_HERE"
}],
}
}
});
Also - you don't need the UsersInList table. Prisma can manage the "join" table under the hood for you if you don't need any extra information or data on that model. Check out the docs here if you want Prisma to manage this table on its own: https://www.prisma.io/docs/concepts/components/prisma-schema/relations/many-to-many-relations

Where to get resources to learn Designing Your Schema for dummies, I tried building my graphql schema using the new transformer v2 (fails)

I tried building my own schema but keep getting
[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'post.save.items')]
I've been shuffling, changing my schema but it either gives me results I didn't want,lacks values I needed or just keeps failing. I am clearly not getting the hang of it. Is there any article that offers over-simplified explanation?
And in case anyone has got a second to spare, you can take a look at my schema and correct/explain what I am doing wrong. Cheers
My Schema
type User #model {
id: ID!
username: String
posts: [Post] #hasMany
}
type Post #model {
id: ID!
photo:String
userID: ID!
user: User #hasOne(fields: ["userID"])
saves: [Save]#hasMany(fields: ["id"])
}
type Save #model(queries: null) {
id: ID!
userID: ID!
postID: ID!
}
And then I edited my listPosts queries from:
export const listPosts = /* GraphQL */ `
query ListPosts {
listPosts {
items {
id
photo
userID
user {
id
username
}
saves {
nextToken
}
userPostsId
}
nextToken
}
}
`;
To:
export const listPosts = /* GraphQL */ `
query ListPosts {
listPosts {
items {
id
photo
userID
user {
id
username
}
saves {
items {
id
userID
postID
}
userPostsId
}
nextToken
}
}
`;
My initial intention was to have a user register with a simple username. Get presented with a list of posts(photos) and save any of them if they wanted to.
A save would be made up of an object containing the save id,the user's id and the post id.
If I query for listPosts all the photos are successfully relayed, but it lacks the save object and throws the error :[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'post.save.items')]

NextAuth Credentials, adding more to the user scheme

Nextauth with mysql persisting users.
I'm trying out this NextAuth thing to see if this is something I like. So far so good. There is one thing tho which is buggin me and that would be the user scheme. By default it returns a name, image and the last one I forgot.
I'd like to add more to this scheme and found some ways to do it by looking at google, however those I tried did not work.
One example I found is by extending the model which clearly makes sense...
The issue here is then me, I do not know what to change in the code below to make it work with my NextAuth credentials provider. As shown below, this doesnt work.
projectfolder -> models -> index.js
import User, { UserSchema } from "./User"
export default {
User: {
model: User,
schema: UserSchema
}
}
projectfolder -> models -> user.js
import Adapters from "next-auth/adapters"
// Extend the built-in models using class inheritance
export default class User extends Adapters.TypeORM.Models.User.model {
constructor(name, email, image, emailVerified, roles) {
super(name, email, image, emailVerified)
if (roles) { this.roles = roles}
}
}
export const UserSchema = {
name: "User",
target: User,
columns: {
...Adapters.TypeORM.Models.User.schema.columns,
roles: {
type: "varchar",
nullable: true
},
},
}
In my [...nextauth].js file I have my provider, in this provider i've added an profile() field with the extra fields. This did not solve the issue.
profile(profile) {
return {
name: profile.name,
email: profile.email,
role: profile.role
};
},
Please correct me if I am wrong but if I am using credentials, then I need to replace the "TypeORM" with something else, correct? How about the path for the files, are they correct?
This should clearly be quite easy but am I missing something or am I doing something wrong? I feel like there is a lack of documentation on extending the user model for mysql.
I've doubled checked that the role is being retrieved from the database and then added to the user variable shown here:
async authorize ....
const user = {
name: result.display_name,
role: result.role_name,
email: result.email
}
Although I can see the role being set in the variable with my console.log(), I still cannot access the role and that I suspect is because of the model. How would I resolve this? Thanks a lot in advance.
Any ideas?
----------------------- UPDATES ------------------------
Btw, here is my callback
callbacks: {
async signIn({ user, account, profile, email }) {
console.log("user", user);
return true;
},
},
and this is what it returns (shortened)
token: {
token: { name: 'Firstname Lastname', email: 'test#mail.com' },
user: {
name: 'Firstname Lastname',
role: 'administrator',
email: 'test#mail.com'
},
account: { type: 'credentials', provider: 'credentials' },
isNewUser: false,
iat: 1634193197,
exp: 1636785197
}
I'm new to TypeORM and I am facing the same problems as people here.
What I've done was create a separate Entity which I called users_info to store the other information and retrieve it after signing in.
It looks like this:
import { UserEntity } from './NextAuthEntities';
#Entity({ name: 'users_info' })
export class MemberEntity {
#PrimaryGeneratedColumn('increment')
id!: number;
#OneToOne(() => UserEntity)
#JoinColumn({
name: 'auth_id',
referencedColumnName: 'id',
})
auth_id!: UserEntity;
#Column({ type: 'varchar', nullable: true })
full_name!: string | null;
// etc
}
Then, I created a handshake API route to retrieve users_info if the user is signed-in.
When I added a new #Column on my custom UsersEntity, it threw me an error when I tried to login. It seems like TypeORMLegacyAdapter can't be extended or be different from the default UserEntity.
Hope it helps

Apollo client and graphQl query error : Variable '$where' expected value of type 'OrganizationWhereUniqueInput

Hi I'm making a backend server with GraphQL, Apollo client & Prisma. I'm trying to write a query where I get organization data back. The user who sends the query should get its organization data back based on their id. When running the query in playground I get this error.
error:
"message": "Variable '$where' expected value of type 'OrganizationWhereUniqueInput!' but got: {\"employees\":{\"id\":\"ckas83z13t9qk0992pucglc4k\"}}. Reason: 'employees' Field 'employees' is not defined in the input type 'OrganizationWhereUniqueInput'. (line 1, column 8):\nquery ($where: OrganizationWhereUniqueInput!) {\n ^",
I don't see what I did wrong. I'm still pretty new to it all. I tried to write the function in Query.js in different ways but no luck. Also, I still find the error messages you get in playground very confusing
schema:
type Query {
getOrganization: Organization!
}
type Organization {
id: ID!
name: String!
country: String!
street: String!
zipCode: Int!
houseNumber: Int!
addings: String
employees: [User!]
}
type User {
id: ID!
firstname:String!
lastname:String!
email: String!
services: [Service!]
organization: Organization!
}
query.js
function getOrganization(parent, args, context, info){
const userId = getUserId(context)
return context.prisma.organization({employees:{id:userId}})
}
// also tried this
/*
function getOrganization(parent, args, context, info){
const userId = getUserId(context)
return context.prisma.organization({where: {employees:{id:userId}}})
}*/
User.js
function services (parent, args, context){
return context.prisma.user({id: parent.id}).services()
}
function organization (parent, args, context){
return context.prisma.user({id: parent.id}).organization()
}
module.exports={
services,
organization
}
Organization.js
function employees(parent, args, context){
return context.prisma.organization({id: parent.id}).employees()
}
module.exports={
employees
}
Could anyone help me see what went wrong?
query in playground:
query{
getOrganization{
name
id
}}
HTTP HEADER:
{
"Authorization": "Bearer {contains user token }"
}
Just use OrganizationWhereInput instead of OrganizationWhereUniqueInput. It will return a list of organisations instead of a single result (might return an empty array), yet it should allow you to search for an organisation using an employee id.

Categories