Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to run mutation on join table created by amplify after v6 upgrade #13226

Open
3 tasks done
duranmla opened this issue Apr 9, 2024 · 0 comments
Open
3 tasks done
Assignees
Labels
DataStore Related to DataStore category pending-triage Issue is pending triage

Comments

@duranmla
Copy link

duranmla commented Apr 9, 2024

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

DataStore

Amplify Version

v6

Amplify Categories

No response

Backend

Amplify CLI

Environment information

# Put output below this line
 System:
    OS: macOS 14.2.1
    CPU: (12) arm64 Apple M2 Max
    Memory: 1.45 GB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 16.20.2 - ~/.nvm/versions/node/v16.20.2/bin/node
    Yarn: 1.22.21 - ~/.nvm/versions/node/v16.20.2/bin/yarn
    npm: 8.19.4 - ~/.nvm/versions/node/v16.20.2/bin/npm
  Browsers:
    Chrome: 123.0.6312.107
    Safari: 17.2.1
  npmPackages:
    @aws-amplify/ui-react: ^6.1.5 => 6.1.6 
    @aws-amplify/ui-react-internal:  undefined ()
    @aws-crypto/sha256-js: ^2.0.1 => 2.0.2 (5.2.0, 3.0.0)
    @commitlint/cli: ^17.6.5 => 17.8.1 
    @commitlint/config-conventional: ^17.6.5 => 17.8.1 
    @craco/craco: ^7.1.0 => 7.1.0 
    @cypress/angular:  0.0.0-development 
    @cypress/mount-utils:  0.0.0-development 
    @cypress/react:  0.0.0-development 
    @cypress/react18:  0.0.0-development 
    @cypress/svelte:  0.0.0-development 
    @cypress/vue:  0.0.0-development 
    @cypress/vue2:  0.0.0-development 
    @emotion/is-prop-valid: ^1.2.2 => 1.2.2 (0.8.8, 1.2.1)
    @jackfranklin/test-data-bot: ^2.1.0 => 2.1.0 
    @react-spring/three: 9.6.1 => 9.6.1 
    @react-theming/storybook-addon: ^1.1.10 => 1.1.10 
    @react-three/drei: ^9.102.6 => 9.102.6 
    @react-three/fiber: ^8.15.19 => 8.15.19 
    @reduxjs/toolkit: ^1.8.3 => 1.9.7 
    @reduxjs/toolkit-query:  1.0.0 
    @reduxjs/toolkit-query-react:  1.0.0 
    @sentry/react: ^7.105.0 => 7.105.0 
    @sentry/tracing: ^7.105.0 => 7.105.0 
    @sentry/webpack-plugin: ^2.14.2 => 2.14.2 
    @storybook/addon-essentials: ^7.6.17 => 7.6.17 
    @storybook/addon-links: ^7.6.17 => 7.6.17 
    @storybook/addons: ^7.6.17 => 7.6.17 (6.5.16)
    @storybook/builder-webpack5: ^7.6.17 => 7.6.17 
    @storybook/manager-webpack5: ^6.5.16 => 6.5.16 
    @storybook/preset-create-react-app: ^7.6.17 => 7.6.17 
    @storybook/react: ^7.6.17 => 7.6.17 
    @storybook/theming: ^7.6.17 => 7.6.17 (6.5.16)
    @svgr/plugin-prettier: 8.1.0 => 8.1.0 
    @testing-library/cypress: ^10.0.1 => 10.0.1 
    @testing-library/dom: ^9.3.4 => 9.3.4 
    @testing-library/jest-dom: ^6.4.2 => 6.4.2 
    @testing-library/react: ^14.2.1 => 14.2.1 
    @testing-library/user-event: ^14.5.2 => 14.5.2 
    @types/draft-js: ^0.11.18 => 0.11.18 (0.11.17)
    @types/draftjs-to-html: ^0.8.4 => 0.8.4 
    @types/html-to-draftjs: ^1.4.3 => 1.4.3 
    @types/jest: ^29.5.12 => 29.5.12 
    @types/lodash: ^4.14.202 => 4.14.202 
    @types/mixpanel-browser: ^2.49.0 => 2.49.0 
    @types/node: ^20.11.25 => 20.11.25 (20.5.1, 18.19.22, 16.18.80)
    @types/react: ^18.2.64 => 18.2.64 
    @types/react-dom: ^18.2.21 => 18.2.21 (18.2.19)
    @types/react-draft-wysiwyg: ^1.13.8 => 1.13.8 
    @types/react-html-parser: ^2.0.6 => 2.0.6 
    @types/react-redux: ^7.1.33 => 7.1.33 
    @types/react-router-dom: ^5.3.3 => 5.3.3 
    @types/react-slider: ^1.3.6 => 1.3.6 
    @types/redux-logger: ^3.0.13 => 3.0.13 
    @types/styled-components: ^5.1.34 => 5.1.34 
    @types/styled-system: ^5.1.22 => 5.1.22 
    @types/styled-system__css: ^5.0.21 => 5.0.21 
    @types/three: ^0.162.0 => 0.162.0 
    aws-amplify: ^6.0.19 => 6.0.27 
    aws-amplify/adapter-core:  undefined ()
    aws-amplify/analytics:  undefined ()
    aws-amplify/analytics/kinesis:  undefined ()
    aws-amplify/analytics/kinesis-firehose:  undefined ()
    aws-amplify/analytics/personalize:  undefined ()
    aws-amplify/analytics/pinpoint:  undefined ()
    aws-amplify/api:  undefined ()
    aws-amplify/api/server:  undefined ()
    aws-amplify/auth:  undefined ()
    aws-amplify/auth/cognito:  undefined ()
    aws-amplify/auth/cognito/server:  undefined ()
    aws-amplify/auth/enable-oauth-listener:  undefined ()
    aws-amplify/auth/server:  undefined ()
    aws-amplify/data:  undefined ()
    aws-amplify/data/server:  undefined ()
    aws-amplify/datastore:  undefined ()
    aws-amplify/in-app-messaging:  undefined ()
    aws-amplify/in-app-messaging/pinpoint:  undefined ()
    aws-amplify/push-notifications:  undefined ()
    aws-amplify/push-notifications/pinpoint:  undefined ()
    aws-amplify/storage:  undefined ()
    aws-amplify/storage/s3:  undefined ()
    aws-amplify/storage/s3/server:  undefined ()
    aws-amplify/storage/server:  undefined ()
    aws-amplify/utils:  undefined ()
    aws-sdk: ^2.1572.0 => 2.1572.0 
    base64-js: ^1.5.1 => 1.5.1 
    cypress: ^13.6.6 => 13.6.6 
    cz-conventional-changelog: 3.3.0 => 3.3.0 
    dayjs: ^1.11.10 => 1.11.10 
    easymde: ^2.18.0 => 2.18.0 
    eslint-config-prettier: ^9.1.0 => 9.1.0 
    eslint-plugin-cypress: ^2.15.1 => 2.15.1 
    eslint-plugin-prettier: ^5.1.3 => 5.1.3 
    formik: ^2.4.5 => 2.4.5 
    framer-motion: ^11.0.8 => 11.0.8 
    history: ^5.3.0 => 5.3.0 
    husky: ^8.0.1 => 8.0.3 
    lodash: ^4.17.21 => 4.17.21 
    mixpanel-browser: ^2.49.0 => 2.49.0 
    polished: ^4.3.1 => 4.3.1 
    postcss-normalize: ^10.0.1 => 10.0.1 
    prettier: ^3.2.5 => 3.2.5 (2.8.8)
    pretty-quick: ^4.0.0 => 4.0.0 
    react: ^18.2.0 => 18.2.0 
    react-accessible-dropdown-menu-hook: ^4.0.0 => 4.0.0 
    react-country-region-selector: ^3.6.1 => 3.6.1 
    react-dom: ^18.2.0 => 18.2.0 
    react-error-boundary: ^4.0.13 => 4.0.13 
    react-feather: ^2.0.10 => 2.0.10 
    react-feature-flags: ^1.0.0 => 1.0.0 
    react-graceful-image: ^1.5.0 => 1.5.0 
    react-helmet-async: ^2.0.4 => 2.0.4 
    react-hot-toast: ^2.4.1 => 2.4.1 
    react-html-parser: ^2.0.2 => 2.0.2 
    react-html-parser-demo:  0.0.0 
    react-loader-spinner: ^6.1.6 => 6.1.6 
    react-markdown: ^9.0.1 => 9.0.1 
    react-redux: ^9.1.0 => 9.1.0 
    react-router-dom: ^6.22.2 => 6.22.2 
    react-scripts: ^5.0.1 => 5.0.1 
    react-select: ^5.8.0 => 5.8.0 
    react-simplemde-editor: ^5.2.0 => 5.2.0 
    react-snap: ^1.23.0 => 1.23.0 
    redux-persist: ^6.0.0 => 6.0.0 
    redux-persist/integration/react:  undefined ()
    remark-gfm: ^4.0.0 => 4.0.0 
    styled-components: ^6.1.8 => 6.1.8 
    styled-components/native:  undefined ()
    styled-system: ^5.1.5 => 5.1.5 
    three: ^0.162.0 => 0.162.0 
    ts-morph: ^21.0.1 => 21.0.1 
    typescript: ^4.7.4 => 4.9.5 (5.3.3)
    wait-on: ^7.2.0 => 7.2.0 
    web-vitals: ^3.5.2 => 3.5.2 
    webpack: ^5.90.3 => 5.90.3 (5.90.1, 4.47.0)
  npmGlobalPackages:
    @aws-amplify/cli: 12.10.1
    commitizen: 4.3.0
    corepack: 0.17.0
    npm: 8.19.4
    yarn: 1.22.21

Describe the bug

TL;DR: The @manyToMany relationship isn't utilizing the "userPool" auth mode in DataStore, causing failed requests to mutate the join table as "Unauthorized". Manually adding the join table resolves this issue but introduces additional negative side effects.

CleanShot 2024-04-03 at 17 40 17@2x

Error: "Ensure app code is up to date, auth directives exist and are correct on each model, and that server-side data has not been invalidated by a schema change. If the problem persists, search for or create an issue: https://github.com/aws-amplify/amplify-js/issues"

As you see it is obvious it will fail as it is attempting to mutate using apiKey instead changing to use userPool. It does change the auth mode for creating the Project but go back to the apiKey for the join table

I have a schema where the Project model is related to the Participant model via a @manyToMany relationship. In my application, when a user creates a project, the code first creates or finds an organizer and a participant, and then attempts to create a record in the join table.

I initially tried creating the join table manually and applying authorization policies on my own, which worked. However, I encountered issues with AppSync where I could retrieve tags using the join table, but I couldn't query tags directly from the Project model, which is essential for certain functionalities within the app.

Expected behavior

I would have expected that the relationship will work as it used to do for version 5. I haven't spot updates that I needed to do on the schema to make this work according to https://docs.amplify.aws/javascript/build-a-backend/graphqlapi/data-modeling/#many-to-many-relationship and https://docs.amplify.aws/javascript/build-a-backend/more-features/datastore/relational-models/#many-to-many-relationships it seems it should work. Maybe I'm missing something?

Reproduction steps

  1. Add a model with @manyToMany relationships that need special auth directives (like private for edit and public only reads)
  2. Try to mutate the join table via DataStore
  3. It seems to fail

Code Snippet

type Project
  @model
  @auth(
    rules: [
      { allow: private }
      { allow: public, operations: [read], provider: apiKey }
      # This allow lambda to make CRUD operations
      { allow: private, provider: iam }
    ]
  ) {
  id: ID!
  # ...more attributes I'm removing for convenience
  tags: [Tag] @manyToMany(relationName: "ProjectTagConnection")
  organisers: [Organiser] @manyToMany(relationName: "ProjectOrganiserConnection")
  participants: [Participant]
    @manyToMany(relationName: "ProjectParticipantConnection")
  posts: [Post] @hasMany(indexName: "byProject", fields: ["id"])
}

As you see the Project has several "manyToMany" relationships with other models such Participants (see model below). Since I have upgraded to version 6 the relationship doesn't work properly anymore as I'm not able to mutate on the connection model due to auth errors.

type Participant
  @model
  @auth(
    rules: [
      { allow: private, operations: [read, create, update] }
      { allow: public, operations: [read], provider: apiKey }
    ]
  ) {
  id: ID!
   # ...more attributes I'm removing for convenience
  projects: [Project] @manyToMany(relationName: "ProjectParticipantConnection")
  posts: [Post] @hasMany(indexName: "byParticipant", fields: ["id"])
}
async submitProject(
    data: Pick<
      Project,
      | 'description'
      | 'image'
      | 'launchPlan'
      | 'location'
      | 'mediaHTML'
      | 'name'
      | 'problem'
      | 'solution'
    >
  ): Promise<Project> {
    const cognitoUser = await getCurrentUser()
    const userId = cognitoUser.userId

    const [organiser, participant, project] = await Promise.all([
      this.findOrCreateOrganiser(userId),
      this.findOrCreateParticipant(userId),
      await DataStore.save(
        new Project({
          name: data.name,
          description: data.description,
          location: data.location,
          problem: data.problem,
          solution: data.solution,
          launchPlan: data.launchPlan,
          status: ProjectStatus.DRAFT,
          mediaHTML: data.mediaHTML,
          image: data.image,
        })
      ),
    ])

    // This are the ones that fail
    await Promise.all([
      DataStore.save(
        new ProjectOrganiserConnection({
          project,
          organiser,
        })
      ),
      DataStore.save(
        new ProjectParticipantConnection({
          project,
          participant,
        })
      ),
    ])

    let listener: any
    return await new Promise((resolve, reject) => {
      /**
       * Wait for datastore to sync with the server
       */
      listener = Hub.listen('datastore', (hubData) => {
        const { event, data } = hubData.payload
        if (event === 'outboxStatus') {
          if ((data as { isEmpty: boolean })?.isEmpty) {
            resolve(project)
            listener()
          }
        }
      })

      /**
       * If the sync takes too long, reject the promise
       */
      setTimeout(async () => {
        // this actually doesn't work as I want, since the conflict resolution says "NO". But this is not the real problem I want to solve.
        if (project) DataStore.delete(project)
        reject('Timeout')
      }, 10000)
    })
  }

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

If for some reason the only solution is to create manually the join tables, I don't have issue with it but I wonder if the expected result is for it to work like this:

CleanShot 2024-04-09 at 08 39 03@2x
Fig. 1 This is how it looks with @manyToMany. Ideal

CleanShot 2024-04-05 at 17 06 30@2x
Fig. 2. This is how it looks when querying with the manual join tables in place

CleanShot 2024-04-05 at 17 07 12@2x
Fig. 3. Prove that the record on the join table exists

@duranmla duranmla added the pending-triage Issue is pending triage label Apr 9, 2024
@jimblanc jimblanc added GraphQL Related to GraphQL API issues DataStore Related to DataStore category and removed GraphQL Related to GraphQL API issues labels Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DataStore Related to DataStore category pending-triage Issue is pending triage
Projects
None yet
Development

No branches or pull requests

3 participants