@0x1a4f7d58 Have you tried some Prisma nested reads?
Prisma Client provides convenient queries for working with relations, such as a fluent API, nested writes (transactions), nested reads and relation filters.
In their example:
const getUser = await prisma.user.findUnique({
where: {
id: 1,
},
// select: { name: true } <-- Can't do this!
include: {
posts: {
select: {
title: true,
},
},
},
})
and that returns
{
"id": 1,
"name": null,
"email": "martina@prisma.io",
"profileViews": 0,
"role": "USER",
"coinflips": [],
"posts": [
{
"title": "How to grow salad"
},
{
"title": "How to ride a horse"
}
]
}
you might be able to do
const user = await db.user.findUnique({
where: { uuid: uuid },
include: {
account: {
select: {
slug: true,
},
}
})
And the user would have all the user attributes and probably an account with a slug attribute.
I think.
const enrichedUserObj = { ...decoded, user.accountId, user.account.slug }
Or, you could just do a nested query on the account
and include, join in on the user uuid to return the id of the account and its slug.
That way it is one single db call.
1 Like
@dthyresson Thanks. The query works, however, passing the values to the enriched object doesnāt. Iām OK to leave as is for the time being but get that it would be better to do it in a single query.
> db.user.findUnique({where: {uuid: 'cfe9bd27-0122-4fe8-ab58-3f7a6bfb6d43'}, include: {account: {select: {slug: true}}}})
{
id: 126,
uuid: 'cfe9bd27-0122-4fe8-ab58-3f7a6bfb6d43',
createdAt: 2021-07-08T14:37:34.877Z,
updatedAt: 2021-07-08T14:37:34.877Z,
email: 'email@email.com',
name: null,
accountId: 130,
account: { slug: 'V52NDE' }
}
Ah, I was coding on the fly, I bet it should be
const enrichedUserObj = { ...decoded, accountId: user.accountId, slug: user.account.slug }
1 Like
Thanks @dthyresson . That works
I have been experimenting with userMetadata
and itās not clear how I can write to either userMetadata.app_metadata
or userMetadata.user_metadata
.
Supabase API :
UPDATE USER
const { user, error } = await supabase.auth.update({
email: "new@email.com",
password: "new-password",
data: { hello: 'world' }
})
Should I write a custom function for this? Something like Update user data Ā· Issue #23 Ā· supabase/supabase-js Ā· GitHub
I noticed in Docs - Authentication : RedwoodJS Docs that there is an Auth Provider Specific Integration section for most providers but not yet Supabase. If you know of an example or can provide any guidance, that would great. I can update the docs once clear.
Btw, this post might be more suited to this thread Supabase redwood experiments
Hi @0x1a4f7d58 Actually I think thatās exactly how you would update the user metdata:
0x1a4f7d58:
UPDATE USER
const { user, error } = await supabase.auth.update({
email: "new@email.com",
password: "new-password",
data: { hello: 'world' }
})
But, you can still to this in a service.
Create SDL for a Mutation that defines the attributes you want in userMetadata (you could do JSON, but better probably if you set). Call that mutation maybe updateUserMetadata()
In a userMetadata
service, create you update method
That method should
requireAuth so you make sure they are logged in
create an instance of the supabase client but instead of using the non key or the superadmin service role key, use the currentUserās token. That will say that the supabase client can only act on that userās info
then supabase.auth.update()
the input data from your mutation
You should be able to get the bearer token using context[1].token
So in that service, your supabase client is:
const supabase = createClient('https://xyzcompany.supabase.co', context[1].token)
// ...
const { user, error } = await supabase.auth.update({
data: { hello: 'world' }
})
export type AuthContextPayload = [
string | Record<string, unknown> | null,
{ type: SupportedAuthTypes } & AuthorizationHeader,
{ event: APIGatewayProxyEvent; context: GlobalContext & LambdaContext }
]
is what context
has so and index 1 is { type: SupportedAuthTypes } & AuthorizationHeader,
and the AuthorizationHeader
has
export interface AuthorizationHeader {
schema: 'Bearer' | 'Basic' | string
token: string
}
So thatās where you get your token to use when creating the supabase client.
I havenāt tried this code, but maybe something like:
// userMetadata.sdl
export const schema = gql`
type UserMetadata {
full_name: String!
favorite_color: String
}
type Query {
userMetadata: UserMetadata!
}
input UpdateUserMetadataInput {
full_name: String
favorite_color: String
}
type Mutation {
updateUserMetadata(input: UpdateUserMetadataInput!): UserMetadata!
}
`
// services/userMetadata.js
import { createClient } from '@supabase/supabase-js'
export const updateUserMetadata = async ({ input }) => {
requireAuth()
const supabase = createClient('https://xyzcompany.supabase.co', context[1].token)
const { user, error } = await supabase.auth.update({
data: { ...input }
})
return user.user_metadata
}
Or something close to that.
1 Like
Hi @dthyresson Thanks! I think Iām v close.
I had to add the following to graphql.js in order for the context to work, as described here
// api/src/functions/graphql.js
import { getAuthenticationContext } from '@redwoodjs/api/dist/auth'
export const handler = createGraphQLHandler({
context: async ({ event, context }) => {
const authContext = await getAuthenticationContext({ event, context })
return authContext
},
...
I can make status 200 posts, however, I cannot get the data to save down. I end-up with {"data":{"updateUserMetadata":null}}
This is what Iāve got
// userMetadata.sdl.js
export const schema = gql`
type UserMetadata {
firstName: String
currentLocationId: Int
}
type Query {
userMetadata: UserMetadata
}
input UpdateUserMetadataInput {
firstName: String
currentLocationId: Int
}
type Mutation {
updateUserMetadata(input: UpdateUserMetadataInput!): UserMetadata
}
`
and
//userMetadata.js
import { requireAuth } from 'src/lib/auth'
import { createClient } from '@supabase/supabase-js'
export const updateUserMetadata = async ({ input }) => {
requireAuth()
const supabase = createClient(process.env.SUPABASE_URL, context[1].token)
const { user, error } = await supabase.auth.update({
data: {
firstName: input.firstName,
currentLocationId: input.currentLocationId,
},
// data: { ...input },
})
return user
}
export const userMetadata = () => {
const supabase = createClient(
process.env.SUPABASE_URL,
process.env.SUPABASE_KEY
)
const user = supabase.auth.user()
return user
}
Note: return user.user_metadata
here gives āCannot read property āuser_metadataā of nullā
and then Iām calling the service and passing data on an onClick
event (just for testing purposes)
//HomePage.js
...
const UPDATE_USER_METADATA = gql`
mutation UpdateUserMetadata($input: UpdateUserMetadataInput!) {
updateUserMetadata(input: $input) {
firstName
currentLocationId
}
}
`
const HomePage = () => {
const [update] = useMutation(UPDATE_USER_METADATA, {
onCompleted: async (data) => {
toast.success('User metadata updated!')
},
})
const onClick = async () => {
update({
variables: {
input: { firstName: 'Metadata', currentLocationId: 130 },
},
})
}
...
Appreciate thatās a lot to take in.
Not 100% on this but in your cell handling after the metadata update, instead of showing information from the graphql result, you would want to show the userMetadata
from useAuth()
hook.
What I wonder, though, is if you will need to force Supabase to re-authenticate and update the user metadata is has stored in its session.
You can get the client
from the useAuth()
hook as well and force a re-auth via await reauthenticate()
.
That might help.
1 Like
Also, if you log out and login again, do you see your userMetadata updates?
I donāt. Feels like the mutation is failing and input = null is being passed to the service (supabase.auth.update)
Hereās what I see after I click
I donāt know if you found a solution, I found this: Supabase authentication && reauthenticate
1 Like