Can't make my signup page works

I follow the tutorial and do some tweet on my application, however for sign up it give me something like this

Invalid this.dbAccessor.findUnique() invocation in
V:\Self_Learning\redwoodjs\trml\node_modules@redwoodjs\auth-dbauth-api\dist\DbAuthHandler.js:852:42

849 …userAttributes
850 } = this.params;
851 if (this._validateField(‘username’, username) && this._validateField(‘password’, password)) {
→ 852 const user = await this.dbAccessor.findUnique({
where: {
email: ‘abc@gmail.com’

}
})

Unknown arg `email` in where.email for type UserWhereUniqueInput. Did you mean `id`? Available args:
type UserWhereUniqueInput {
id?: BigInt
}

The database I use is MySQL with snake form in the table, therefore in my schema.prisma file I will have following:

model Role {
  id        BigInt    @id @default(autoincrement()) @db.UnsignedBigInt
  name      String?   @db.VarChar(255)
  createdAt DateTime? @default(now()) @map("create_time") @db.Timestamp(0)
  updatedAt DateTime? @updatedAt @map("update_time") @db.Timestamp(0)
  app_user  User[]

  @@map("app_role")
}

model User {
  id             BigInt    @id @default(autoincrement()) @db.UnsignedBigInt
  lastName       String    @map("last_name") @db.VarChar(255)
  firstName      String    @map("first_name") @db.VarChar(255)
  username       String    @db.VarChar(255)
  email          String    @unique @db.VarChar(255)
  hashedPassword String    @map("password") @db.VarChar(255)
  salt           String
  createdAt      DateTime? @default(now()) @map("create_time") @db.Timestamp(0)
  updatedAt      DateTime? @updatedAt @map("update_time") @db.Timestamp(0)
  role_id        BigInt?   @db.UnsignedBigInt
  app_role       Role?     @relation(fields: [role_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "app_user_app_role_id_fk")
  Player         Player[]

  @@index([role_id], map: "app_user_app_role_id_fk")
  @@map("app_user")
}

And after I run the yarn rw g dbAuth, it created all the files for me successfully, and in the sign up page I added last name and first name into the page, but when I click on SINGUP, it give me the error that I just mentioned above… try to search but I can’t find the solution on that…

EDIT: After I run the yarn rw prisma genarated it didn’t give me that first error which I mentioned above, however I am facing second issue…

Invalid `db.user.create()` invocation in
V:\Self_Learning\redwoodjs\trml\api\src\functions\auth.js:108:22

105 // eslint-disable-next-line camelcase
106 handler: ({ lastName, firstName, username, hashedPassword }) => {
107 console.log(lastName, firstName, username, hashedPassword)
→ 108 return db.user.create({
data: {
+ lastName: String,
+ firstName: String,
email: 'abc@gmail.com',
username: 'abc@gmail.com',
hashedPassword: 'f3e682fa712f0a95eda14448da3c06fa5aad4d23423f32bc5ef907c7d222db20',
...
})

In my signup page I have last name, first name defined but it looks like not carry over to create the user…

EDIT2: Finally found why… in order to pass that lastName and firstName field I need to use userAttributes, but not running into 3rd problem… it save successfully… however I got Do not know how to serialize a BigInt and most of the solutions said… need to serialize and deserialize it by using following code :

JSON.stringify(
  this,
  (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
)

but I don’t know where I should put it…

You can serialize and deserialize a model field in your SDL file. If you started from the tutorial, you probably don’t have a users SDL file, just the User model in your schema.prisma file (which created the table in your MySQL DB when you ran the migration).

The reason is that the various auth strategies use Prisma ORM methods directly on the User table (instead of through the GraphQL layer which need resolvers to make the Prisma ORM method calls), e.g. for dbAuth you’d see something like this in your api/src/lib/auth.{js,ts} file:

export const getCurrentUser = async (session) => {
  ...
  return await db.user.findUnique({
    where: { id: session.id },
    select: { id: true, email: true, roles: true },
  })
}

So you need to add an SDL for User model:

yarn redwood generate sdl User

And then you can add your JSON.stringify call in a mutation resolver (createUser and updateUser or whatever). The file output should be in api/src/services/users. There’s more information in the tutorial.

You might also check the Integer vs. String IDs note in the tutorial.

Tried… here is my users.sdl.js and roles.sdl.js

export const schema = gql`
  type User {
    id: BigInt!
    lastName: String!
    firstName: String!
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    createdAt: DateTime
    updatedAt: DateTime
    role_id: BigInt
    app_role: Role
  }

  type Query {
    users: [User!]! @requireAuth
    user(id: BigInt!): User @requireAuth
  }

  input CreateUserInput {
    lastName: String!
    firstName: String!
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    role_id: BigInt
  }

  input UpdateUserInput {
    lastName: String
    firstName: String
    username: String
    email: String
    hashedPassword: String
    salt: String
    role_id: BigInt
  }

  type Mutation {
    createUser(input: CreateUserInput!): User! @requireAuth
    updateUser(id: BigInt!, input: UpdateUserInput!): User! @requireAuth
    deleteUser(id: BigInt!): User! @requireAuth
  }

`
JSON.stringify(
  this,
  (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
)

export const schema = gql`

  type Role {
    id: BigInt!
    name: String
    createdAt: DateTime
    updatedAt: DateTime
    app_user: [User]!
  }

  type Query {
    roles: [Role!]! @requireAuth
    role(id: BigInt!): Role @requireAuth
  }

  input CreateRoleInput {
    name: String
  }

  input UpdateRoleInput {
    name: String
  }

  type Mutation {
    createRole(input: CreateRoleInput!): Role! @requireAuth
    updateRole(id: BigInt!, input: UpdateRoleInput!): Role! @requireAuth
    deleteRole(id: BigInt!): Role! @requireAuth
  }
`
JSON.stringify(
  this,
  (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
)

It looks like still something not right…

Those are your GraphQL schema definitions. You should also have service files in api/src/services/users and api/src/services/roles that define your resolvers. The resolvers are what Yoga uses to make calls to the Prisma ORM, and are validated against the schemas (SDLs). So you might have a user service that looks like this, where db is an instance of the Prisma client:

import { db } from 'src/lib/db'

export const users = () => {
  return db.user.findMany()
}

export const user = ({ id }) => {
  return db.user.findUnique({
    where: { id },
  })
}

export const User = {
  roles: async (_obj, { root }) => {
    const maybeRoles = await db.user
      .findUnique({
        where: { id: root?.id },
      })
      .roles()

    if (!maybeRoles) {
      throw new Error('Could not resolve roles field on user')
    }

    return maybeRoles
  },
}

export const createUser = ({ input }) => {
  return db.user.create({
    data: input,
  })
}

export const updateUser = async ({  id,  input }) => {
  return db.user.update({
    data: input,
    where: { id },
  })
}

export const deleteUser = ({ id }) => {
  return db.user.delete({
    where: { id },
  })
}

And you could write your stringify procedure into a function and call it from createUser and updateUser. The more canonical Redwood way would be to use a transformer, that way you can just use a decorator on your schema (but you’ll still need the resolvers in api/src/services).

The first two resolvers (users and user) are your item and list queries. The capitalized resolver (User) is automatically created by Redwood if you don’t need to do anything special - read through Understanding Default Resolvers for an explanation. The reason I’ve included it here is to set up the relationship between your User and Role models (note the code example I gave isn’t tested or particularly likely to work as-is).

I notice you use a snake-case name for your role relationship. Redwood has special handling for that; I don’t use it so don’t know what it is off hand (maybe it converts it to camelCase? idk). I would rename it just to roles if you can, it’s more the Redwood way I think. And you have your User model role_id field set to a BigInt in your schema.prisma file - it should be a reference to the model name, not a primary key value:

model User {
  ...
  roles               Role[]
}

But my db design will be 1 user only have 1 role, but same role can be have more than 1 users… (Like userA has role Director (which is also employee), but userB only has role Employee) and this model file was got generate by using yarn rw prisma db pull, even I delete that line it will automatically add this line back…

Here is all the update files I have …
api/src/services/users/users.js

import { db } from 'src/lib/db'

const bcrypt = require('bcryptjs')

export const users = () => {
  JSON.stringify(
    this,
    (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
  )

  return db.user.findMany()
}

export const user = ({ id }) => {
  JSON.stringify(
    this,
    (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
  )

  return db.user.findUnique({
    where: { id },
  })
}

export const createUser = async ({ input }) => {
  const password = await bcrypt.encrypt(input.password, 10)
  const data = { ...input, password }
  JSON.stringify(
    this,
    (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
  )

  return db.user.create({
    data,
  })
}

export const updateUser = ({ id, input }) => {
  return db.user.update({
    data: input,
    where: { id },
  })
}

export const deleteUser = ({ id }) => {
  return db.user.delete({
    where: { id },
  })
}

export const User = {
  role: (_obj, { root }) => {
    return db.user.findUnique({ where: { id: root?.id } }).role()
  },
}

then in my api/src/services/roles/roles.js

import { db } from 'src/lib/db'

export const roles = () => {
  JSON.stringify(
    this,
    (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
  )

  return db.role.findMany()
}

export const role = ({ id }) => {
  JSON.stringify(
    this,
    (key, value) => (typeof value === 'bigint' ? value.toString() : value) // return everything else unchanged
  )

  return db.role.findUnique({
    where: { id },
  })
}

export const createRole = ({ input }) => {
  return db.role.create({
    data: input,
  })
}

export const updateRole = ({ id, input }) => {
  return db.role.update({
    data: input,
    where: { id },
  })
}

export const deleteRole = ({ id }) => {
  return db.role.delete({
    where: { id },
  })
}

export const Role = {
  users: (_obj, { root }) => {
    return db.role.findUnique({ where: { id: root?.id } }).users()
  },
}

in api/src/graphql/roles.sdl.js and api/src/graphql/users.sdl.js

export const schema = gql`
  type Role {
    id: BigInt!
    name: String
    createdAt: DateTime
    updatedAt: DateTime
    users: [User]!
  }

  type Query {
    roles: [Role!]! @requireAuth
    role(id: BigInt!): Role @requireAuth
  }

  input CreateRoleInput {
    name: String
  }

  input UpdateRoleInput {
    name: String
  }

  type Mutation {
    createRole(input: CreateRoleInput!): Role! @requireAuth
    updateRole(id: BigInt!, input: UpdateRoleInput!): Role! @requireAuth
    deleteRole(id: BigInt!): Role! @requireAuth
  }

`


export const schema = gql`
  type User {
    id: BigInt!
    lastName: String!
    firstName: String!
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    createdAt: DateTime
    updatedAt: DateTime
    roleId: BigInt
    role: Role
  }

  type Query {
    users: [User!]! @requireAuth
    user(id: BigInt!): User @requireAuth
  }

  input CreateUserInput {
    lastName: String!
    firstName: String!
    username: String!
    email: String!
    hashedPassword: String!
    salt: String!
    roleId: BigInt
  }

  input UpdateUserInput {
    lastName: String
    firstName: String
    username: String
    email: String
    hashedPassword: String
    salt: String
    roleId: BigInt
  }

  type Mutation {
    createUser(input: CreateUserInput!): User! @requireAuth
    updateUser(id: BigInt!, input: UpdateUserInput!): User! @requireAuth
    deleteUser(id: BigInt!): User! @requireAuth
  }

`
and in my schema.prisma file

model Role {
  id        BigInt    @id @default(autoincrement()) @db.UnsignedBigInt
  name      String?   @db.VarChar(255)
  createdAt DateTime? @default(now()) @map("create_time") @db.Timestamp(0)
  updatedAt DateTime? @default(now()) @updatedAt @map("update_time") @db.Timestamp(0)
  users     User[]

  @@map("app_role")
}

model User {
  id             BigInt    @id @default(autoincrement()) @db.UnsignedBigInt
  lastName       String?   @map("last_name") @db.VarChar(255)
  firstName      String?   @map("first_name") @db.VarChar(255)
  username       String?   @db.VarChar(255)
  email          String?   @unique(map: "app_user_pk") @db.VarChar(255)
  hashedPassword String?   @map("password") @db.VarChar(255)
  createdAt      DateTime? @default(now()) @map("create_time") @db.Timestamp(0)
  updatedAt      DateTime? @default(now()) @updatedAt @map("update_time") @db.Timestamp(0)
  roleId         BigInt?   @map("rold_id") @db.UnsignedBigInt <--- I can't control this one.. if I delete it it still comeback
  role           Role?     @relation(fields: [roleId], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "app_user_app_role_id_fk")


  @@index([roleId], map: "app_user_app_role_id_fk")
  @@map("app_user")
}

I don’t know what else I need to change now…

I haven’t tested this, but hopefully it gets you close. Create a transformer like this:

// api/src/directives/bigInt.directive.js
import { createTransformerDirective } from '@redwoodjs/graphql-server'

export const schema = gql`
  directive @bigInt on FIELD_DEFINITION
`

const transform = ({ resolvedValue }) => {
  /** https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields#serializing-bigint */
  return String(resolvedValue)
}

const bigInt = createTransformerDirective(schema, transform)
export default bigInt

You can use that transformer on query definition fields in your SDLs. I think you should only need it on the type itself (and not on the Query):

// api/src/graphql/roles.sdl.js
export const schema = gql`
  type Role {
    id: BigInt! @bigInt
...

and

// api/src/graphql/users.sdl.js
export const schema = gql`
  type User {
    id: BigInt! @bigInt
...

And remove your stringify methods you added into your service files (api/src/services/users/users.js and api/src/services/roles/roles.js).

Do you have any reason to want your existing MySQL database schema to be your source of authority for your data model (and not to let Redwood handle it for you), e.g. another app that will continue to use the database tables after you put together a Redwood app to use it?