[Solved] Help! Netlify returning 400's [using SupaBase]

Authenticating ok w/SupaBase

I have two very similar api functions user() and events()

Netlify finds and runs user() ok, but returns a 400 on events() – right after succeeding multiple times w/user() – Everything works fine in dev, and most of my other functions work fine in a Netlify deploy…

Nothing shows up in the Netlify log so it makes me think that it’s not getting past the GraphQL server (?)

Odd…

How do I get more insight into why this call is failing?

users.sdl.ts

export const schema = gql`
  type User {
    id: String!
    email: String!
    customer: Customer
    customerId: String
    business: Business
    businessId: String
    owner: Business
    ownerId: String
    purchases: [BusinessPurchase]!
    attendees: [Attendee]!
    events: [Event]!
    canPurchase: Boolean!
    created_at: DateTime!
    updated_at: DateTime!
  }

  type Query {
    user: User
  }

  input CreateUserInput {
    email: String!
    customerId: String
    exployeeId: String
    ownerId: String
    purchaserId: String
    canPurchase: Boolean!
    created_at: DateTime!
    updated_at: DateTime!
  }

  input UpdateUserInput {
    email: String
    customerId: String
    exployeeId: String
    ownerId: String
    purchaserId: String
    canPurchase: Boolean
    created_at: DateTime
    updated_at: DateTime
  }
`;

users.ts

import type { Prisma } from "@prisma/client";
import type { ResolverArgs, BeforeResolverSpecType } from "@redwoodjs/api";

const stringify = require('json-stringify-safe');
import { context } from '@redwoodjs/api'
import { logger } from 'src/lib/logger'
import { thenDebug } from 'src/lib/thenables'

import { db } from "src/lib/db";
import { requireAuth } from "src/lib/auth";

// Used when the environment variable REDWOOD_SECURE_SERVICES=1
export const beforeResolver = (rules: BeforeResolverSpecType) => {
  rules.add(requireAuth);
};

export const user = () => {
  if (context?.currentUser?.sub) {
    return db.user
      .findUnique({
        where: { id: context?.currentUser?.sub  }
      })
      .then(thenDebug('user'))

  } else {
    return Promise.resolve(null)
  }
};

export const User = {
  customer: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).customer(),
  exployee: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).business(),
  owner: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).owner(),
  purchaser: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).purchases(),
  attendees: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).attendees(),
  events: (_obj, { root }: ResolverArgs<ReturnType<typeof User>>) =>
    db.user.findUnique({ where: { id: root.id } }).events(),
};

events.sdl.ts

export const schema = gql`
  type Event {
    id: String!
    business: Business!
    businessId: String!
    planner: User!
    plannerId: String!
    imageStore: ImageStore
    imageStoreId: String
    name: String
    description: String
    attendees: [Attendee]!
    guests: [Guest]!
    images: [Image]!
    questions: [Question]!
    start: DateTime!
    stop: DateTime!
    hasVaxxifi: Boolean!
    hasVaccess: Boolean!
    created_at: DateTime!
    updated_at: DateTime!
  }

  type Query {
    events: [Event]!
    event(id: String): Event
  }

  input CreateEventInput {
    name: String!
    description: String
    start: DateTime
    stop: DateTime
  }

  input UpdateEventInput {
    name: String
    description: String
    hasVaccess: Boolean
    start: DateTime
    stop: DateTime
  }

  type Mutation {
    deleteEvent(id: String!): String
    createEvent(input: CreateEventInput): Event
    updateEvent(id: String!, input: UpdateEventInput): Event
  }
`;

events.ts

import type { Prisma } from "@prisma/client";
import type { ResolverArgs, BeforeResolverSpecType } from "@redwoodjs/api";

const stringify = require('json-stringify-safe');
import { context } from '@redwoodjs/api'
import { logger } from 'src/lib/logger'
import { thenDebug } from 'src/lib/thenables'

import { nanoid } from 'nanoid'

// https://www.prisma.io/docs/reference/api-reference/prisma-client-reference
import { db } from "src/lib/db";

import { requireAuth } from "src/lib/auth";

// Used when the environment variable REDWOOD_SECURE_SERVICES=1
export const beforeResolver = (rules: BeforeResolverSpecType) => {
  rules.add(requireAuth);
};

export const Event = {
  business: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).business(),
  planner: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).planner(),
  imageStore: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).imageStore(),
  attendees: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).attendees(),
  guests: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).guests(),
  images: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).images(),
  questions: (_obj, { root }: ResolverArgs<ReturnType<typeof Event>>) =>
    db.event.findUnique({ where: { id: root.id } }).questions(),
};

export const events = () => {
  try {
    if (context?.currentUser?.sub) {
      return db.event.findMany({
        where: { plannerId: context?.currentUser?.sub }
      })
      .then(thenDebug(`db.event.findMany`))
    } else {
      return Promise.resolve([])
    }
  } catch (err) {
    console.error(`events ~ err`, err)
  }
};

export const event = ({ id }) => {
  return db.event.findUnique({
    where: { id }
  })
    .then(thenDebug(`db.event.findUnique`));
};


export const createEvent = async ({ input }) => {
  if (context?.currentUser?.sub) {

    const newEventId = nanoid()
    const newImageStoreId = nanoid()
    const newS3Bucket = nanoid()

    logger.debug(`createEvent id: ${newEventId}, input:${stringify(input, null, 2)}`)

    const me = await db.user.findUnique({
      where: { id: context?.currentUser?.sub }
    })

    // create an event with it's imageStore
    return await db.event.create({
      data: {
        name: input.name,
        id: newEventId,
        plannerId: me.id,
        businessId: me.businessId,
        imageStore: {
          create: {
            id: newImageStoreId,
            s3Bucket: newS3Bucket
          }
        }
      }
    })
    .then(thenDebug(`db.event.create`))
    .catch(logger.error)

  } else {
    return Promise.resolve(null)
  }
};

export const updateEvent = async ({ id, input }) => {
  if (context?.currentUser?.sub) {
    const updated = await db.event.updateMany({
      where: { id, plannerId: context?.currentUser?.sub },
      data: input
    })
    .then(thenDebug(`db.event.updateMany`))
    .catch(logger.error)

    return db.event.findFirst({
      where: { id, plannerId: context?.currentUser?.sub }
    })
    .then(thenDebug(`.event.findFirst`))
    .catch(logger.error)
  } else {
    return Promise.resolve(null)
  }
};

export const deleteEvent = async ({ id }) => {
  if (context?.currentUser?.sub) {
    return await db.event.delete({
      where: { id }
    })
    // .then(thenDebug(`db.event.delete`))
    .catch(logger.error)
  } else {
    return Promise.resolve(null)
  }
};

netlify.toml

[build]
command = "yarn rw deploy netlify"
publish = "web/dist"
functions = "api/dist/functions"

[dev]
  command = "yarn rw dev"

[[redirects]]
  from = "/*"
  to = "/index.html"
  status = 200

GetUserDataCell.tsx

export  type UserProfileProps = {
  id: string,
  email: string
  customer:  {
    customerPK: string
  }
  canPurchase: boolean
  owner:  {
    businessPK: string
    hasPlanner: boolean
    hasVaxxifi: boolean
    events:  {
      hasVaxxifi: boolean
      hasVaccess: boolean
    }
  }
}

export const beforeQuery = (props) => {
  const variables = { ...props }
  return { variables, fetchPolicy: 'no-cache' }
}

export const QUERY = gql`
  query GetUserDataQuery {
    user {
      id
      email
      customer {
        customerPK
      }
      canPurchase
      owner {
        businessPK
        hasPlanner
        hasVaxxifi
        events {
          hasVaxxifi
          hasVaccess
        }
      }
    }
  }
`

export const Loading = () => null

export const Empty = () => null

export const Failure = ({ error }) => (
  <div style={{ color: 'red' }}>Error: {error?.message}</div>
)

export const Success = ({ user, variables }) => {
  console.debug('user', user)
    if (variables.exfiltrate) {
      setTimeout(() => variables.exfiltrate(user))
    }
  return null
}

GetEventListCell


export const beforeQuery = (props) => {
  const variables = { ...props }
  return { variables, fetchPolicy: 'no-cache' }
}

export const QUERY = gql`
  query GetEventListQuery {
    events {
      id
      name
      description
      start
      stop
      hasVaxxifi
      hasVaccess
      created_at
      updated_at
    }
  }
`

export const Loading = () => null

export const Empty = () => null

export const Failure = ({ error }) => (
  <div style={{ color: 'red' }}>Error: {error?.message}</div>
)

export const Success = ({ events, variables }) => {
  console.debug(`GetEventListQuery ~ events`, events)
  if (variables.exfiltrate) {
    setTimeout(() => variables.exfiltrate(events))
  }
  return null
}

I commented out this, in the hopes it would help – but no luck

// Used when the environment variable REDWOOD_SECURE_SERVICES=1
export const beforeResolver = (rules: BeforeResolverSpecType) => {
  rules.add(requireAuth);
};

This project was started with a version before the REDWOOD_SECURE_SERVICES and has been upgraded – so the newer api functions have been generated w/these

After I get this thing done I’ll try to upgrade again and implement the secure services

So Odd…

all of my other functions are working… I can create an event, add attendees, and let attendees add guests

Hi @ajoslin103 I am trying to follow the code samples but wondering if you have a public repo that you can share instead to look at the entire api — and a public url to try out the app. Thanks

Thanks @dthyresson - not public yet. (and way too shoddy to let you see it :slight_smile:

There were two problems – one, that Cell was the wrong one to be looking at (d’oh!)

This one was the right one:

export const QUERY = gql`
  query EventListQuery {
    events {
      id
      name
      description
      start
      stop
      attendees {
        answers {
          answer
        }
      }
      guests {
        answers {
          answer
        }
      }
      questions {
        title
        description
      }
      hasVaxxifi
      hasVaccess
      created_at
      updated_at
    }
  }
`

from prisma.db

model Question {
  id         String           @id
  attendees  AttendeeAnswer[]
  events     Event[]
  guests     GuestAnswer[]
  questionPK String
}

the question ask was failing because Questions don’t yet have their titles & descriptions…

that it didn’t fail in DEV until I reduced the ask on the attendees and guests to just their id’s – pretty bizarre…

I wish that I had a better insight into why it passed in dev and failed in deployed, prisma says there are no changes to be made…

Given the number of nested resolvers in your query

export const QUERY = gql`
  query EventListQuery {
    events {
      id
      name
      description
      start
      stop
      attendees {
        answers {
          answer
        }
      }
      guests {
        answers {
          answer
        }
      }
      questions {
        title
        description
      }
      hasVaxxifi
      hasVaccess
      created_at
      updated_at
    }
  }
`

There’s a chance that you create a number of N+1 queries: for each event, get each attendees and each answer … same for guests and questions and depending on how your query was written, you could be doing 100’s of database calls – and perhaps that either timed out or used up database connections?

Here’s some Prisma info on N+1

And some background on GraphQL and N+1

https://medium.com/the-marcy-lab-school/what-is-the-n-1-problem-in-graphql-dd4921cb3c1a

You can get a sense of the number of database calls by setting up your local logger to log Prisma at the info level.

ah !! tremendous !! [literally]

And I really only care about COUNT()s here, so…

[search…, search… sear]

Has this worked for you?