aws-amplify / amplify-category-api

The AWS Amplify CLI is a toolchain for simplifying serverless web and mobile development. This plugin provides functionality for the API category, allowing for the creation and management of GraphQL and REST based backends for your amplify project.
https://docs.amplify.aws/
Apache License 2.0
89 stars 79 forks source link

Not Authorized to access on type Mutation - 2 #2970

Open chrisbonifacio opened 1 month ago

chrisbonifacio commented 1 month ago

Amplify CLI Version

N/A

Question

Keep in mind I replaced some sensitive info with placeholders.

Here is the schema(schema.ql.ts) auto generated using this npx ampx generate schema-from-database --connection-uri-secret SQL_CONNECTION_STRING --out amplify/data/schema.sql.ts.

/* eslint-disable */
/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. */
import { a } from "@aws-amplify/data-schema";
import { configure } from "@aws-amplify/data-schema/internals";
import { secret } from "@aws-amplify/backend";

export const schema = configure({
    database: {
        identifier: "DATABASE_IDENTIFIER", // Placeholder for database identifier
        engine: "postgresql",
        connectionUri: secret("YOUR_SQL_CONNECTION_STRING"), // Placeholder for SQL connection string
        vpcConfig: {
            vpcId: "YOUR_VPC_ID", // Placeholder for VPC ID
            securityGroupIds: [
                "YOUR_SECURITY_GROUP_ID_1", // Placeholder for Security Group ID 1
                "YOUR_SECURITY_GROUP_ID_2"  // Placeholder for Security Group ID 2
            ],
            subnetAvailabilityZones: [
                {
                    subnetId: "YOUR_SUBNET_ID_1", // Placeholder for Subnet ID 1
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_1" // Placeholder for Availability Zone 1
                },
                {
                    subnetId: "YOUR_SUBNET_ID_2", // Placeholder for Subnet ID 2
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_2" // Placeholder for Availability Zone 2
                },
                {
                    subnetId: "YOUR_SUBNET_ID_3", // Placeholder for Subnet ID 3
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_3" // Placeholder for Availability Zone 3
                },
                {
                    subnetId: "YOUR_SUBNET_ID_4", // Placeholder for Subnet ID 4
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_4" // Placeholder for Availability Zone 4
                },
                {
                    subnetId: "YOUR_SUBNET_ID_5", // Placeholder for Subnet ID 5
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_5" // Placeholder for Availability Zone 5
                },
                {
                    subnetId: "YOUR_SUBNET_ID_6", // Placeholder for Subnet ID 6
                    availabilityZone: "YOUR_AVAILABILITY_ZONE_6" // Placeholder for Availability Zone 6
                }
            ]
        }
    }
}).schema({
    "demographic_data": a.model({
        demographic_data_id: a.integer().required(),
        user_id: a.integer().required(),
        country: a.string(),
        state: a.string(),
        tribe: a.string(),
        height: a.integer(),
        weight: a.integer(),
        sex: a.string(),
        ethnic_group: a.string(),
        physical_activity_level: a.string(),
        smoking_history: a.string(),
        occupational_exposure: a.string(),
        previous_disease: a.string(),
        respiratory_medication_use: a.string(),
        pregnant: a.string()
    }).identifier([
        "demographic_data_id"
    ]),
    "test": a.model({
        test_id: a.integer().required(),
        source_id: a.integer().required(),
        user_name: a.string().required(),
        user_id: a.integer(),
        created_timestamp: a.string(),
        note: a.string(),
        input_filename: a.string(),
        demographic_data_id: a.integer()
    }).identifier([
        "test_id"
    ]),
    "test_data": a.model({
        test_data_id: a.integer().required(),
        test_id: a.integer().required(),
        source_id: a.integer().required(),
        time_stamp: a.string().required(),
        type: a.string(),
        fev1: a.float(),
        fvc: a.float(),
        fev1_over_fvc: a.float(),
        ivc: a.float(),
        pef: a.float(),
        heart_rate: a.float(),
        pressure: a.float(),
        air_quality: a.float(),
        co2: a.float()
    }).identifier([
        "test_data_id"
    ]),
    "user": a.model({
        user_id: a.integer().required(),
        email: a.string(),
        first: a.string(),
        last: a.string(),
        date_of_birth: a.date()
    }).identifier([
        "user_id"
    ])
});

Here is my data/resource.ts

import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { websiteEmbed } from "../functions/website-embed/resource";
// Import the generated schema for your PostgreSQL database
import { schema as generatedSqlSchema } from './schema.sql';

// Define your existing schema (websiteEmbed ) 
const websiteEmbed Schema = a.schema({
  websiteEmbed: a
    .query()
    .arguments({
      email: a.string(),
      visualId: a.string(),
    })
    .returns(a.string())
    .handler(a.handler.function(websiteEmbed ))
    .authorization((allow) => allow.authenticated()),
});

// Wrap the generated SQL schema and add authentication authorization
const testSchema = generatedSqlSchema.authorization((allow) => allow.authenticated());

// Combine the schemas
const combinedSchema = a.combine([websiteEmbed Schema, testSchema]);

// Define the client schema type for both combined schemas
export type Schema = ClientSchema<typeof combinedSchema>;

// Define data with combined schema
export const data = defineData({
  schema: combinedSchema,
  authorizationModes: {
    defaultAuthorizationMode: "userPool",
  },
});

Here is my front end code:

import React, { useState, useEffect } from 'react';
import type { Schema } from './../../amplify/data/resource';
import { generateClient } from 'aws-amplify/api';

const YourComponent: React.FC = () => {
  const [events, setEvents] = useState<any[]>([]);
  const [loading, setLoading] = useState<boolean>(true);
  const [error, setError] = useState<string | null>(null);
  const client = generateClient<Schema>();

  useEffect(() => {
    const fetchEvents = async () => {
      try {
        const response = await client.models.demographic_data.list();
        console.log('API Response:', response);

        // Check if response has a 'data' property and set it accordingly
        if (Array.isArray(response.data)) {
          setEvents(response.data);
        } else {
          setEvents([]);
        }
      } catch (err) {
        console.error('Error fetching events:', err);
        setError('Failed to fetch events');
      } finally {
        setLoading(false);
      }
    };

    fetchEvents();
  }, [client]);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error}</p>;

  return (
    <div>
      <h1>Test Data</h1>
      {events.length > 0 ? (
        <ul>
          {events.map((event, index) => (
            <li key={index}>{JSON.stringify(event)}</li>
          ))}
        </ul>
      ) : (
        <p>No events found.</p>
      )}
    </div>
  );
};

export default YourComponent;

I know this might be overkill but I want to provide as much as I can.

I think this might have been what you were looking for. I just posted the queries file. I used this command to generate it npx ampx generate graphql-client-code --format graphql-codegen --statement-target typescript --out ./src/graphql/. Not really sure how this file is then referenced...

API.ts

/* tslint:disable */
/* eslint-disable */
// this is an auto generated file. This will be overwritten

import * as APITypes from "./API";
type GeneratedQuery<InputType, OutputType> = string & {
  __generatedQueryInput: InputType;
  __generatedQueryOutput: OutputType;
};

export const getDemographic_data = /* GraphQL */ `query GetDemographic_data($demographic_data_id: Int!) {
  getDemographic_data(demographic_data_id: $demographic_data_id) {
    country
    demographic_data_id
    ethnic_group
    height
    occupational_exposure
    physical_activity_level
    pregnant
    previous_disease
    respiratory_medication_use
    sex
    smoking_history
    state
    tribe
    user_id
    weight
    __typename
  }
}
` as GeneratedQuery<
  APITypes.GetDemographic_dataQueryVariables,
  APITypes.GetDemographic_dataQuery
>;
export const getTest = /* GraphQL */ `query GetTest($test_id: Int!) {
  getTest(test_id: $test_id) {
    created_timestamp
    demographic_data_id
    input_filename
    note
    source_id
    test_id
    user_id
    user_name
    __typename
  }
}
` as GeneratedQuery<APITypes.GetTestQueryVariables, APITypes.GetTestQuery>;
export const getTest_data = /* GraphQL */ `query GetTest_data($test_data_id: Int!) {
  getTest_data(test_data_id: $test_data_id) {
    air_quality
    co2
    fev1
    fev1_over_fvc
    fvc
    heart_rate
    ivc
    pef
    pressure
    source_id
    test_data_id
    test_id
    time_stamp
    type
    __typename
  }
}
` as GeneratedQuery<
  APITypes.GetTest_dataQueryVariables,
  APITypes.GetTest_dataQuery
>;
export const getUser = /* GraphQL */ `query GetUser($user_id: Int!) {
  getUser(user_id: $user_id) {
    date_of_birth
    email
    first
    last
    user_id
    __typename
  }
}
` as GeneratedQuery<APITypes.GetUserQueryVariables, APITypes.GetUserQuery>;
export const listDemographic_data = /* GraphQL */ `query ListDemographic_data(
  $demographic_data_id: Int
  $filter: ModelDemographic_dataFilterInput
  $limit: Int
  $nextToken: String
  $sortDirection: ModelSortDirection
) {
  listDemographic_data(
    demographic_data_id: $demographic_data_id
    filter: $filter
    limit: $limit
    nextToken: $nextToken
    sortDirection: $sortDirection
  ) {
    items {
      country
      demographic_data_id
      ethnic_group
      height
      occupational_exposure
      physical_activity_level
      pregnant
      previous_disease
      respiratory_medication_use
      sex
      smoking_history
      state
      tribe
      user_id
      weight
      __typename
    }
    nextToken
    __typename
  }
}
` as GeneratedQuery<
  APITypes.ListDemographic_dataQueryVariables,
  APITypes.ListDemographic_dataQuery
>;
export const listTest_data = /* GraphQL */ `query ListTest_data(
  $filter: ModelTest_dataFilterInput
  $limit: Int
  $nextToken: String
  $sortDirection: ModelSortDirection
  $test_data_id: Int
) {
  listTest_data(
    filter: $filter
    limit: $limit
    nextToken: $nextToken
    sortDirection: $sortDirection
    test_data_id: $test_data_id
  ) {
    items {
      air_quality
      co2
      fev1
      fev1_over_fvc
      fvc
      heart_rate
      ivc
      pef
      pressure
      source_id
      test_data_id
      test_id
      time_stamp
      type
      __typename
    }
    nextToken
    __typename
  }
}
` as GeneratedQuery<
  APITypes.ListTest_dataQueryVariables,
  APITypes.ListTest_dataQuery
>;
export const listTests = /* GraphQL */ `query ListTests(
  $filter: ModelTestFilterInput
  $limit: Int
  $nextToken: String
  $sortDirection: ModelSortDirection
  $test_id: Int
) {
  listTests(
    filter: $filter
    limit: $limit
    nextToken: $nextToken
    sortDirection: $sortDirection
    test_id: $test_id
  ) {
    items {
      created_timestamp
      demographic_data_id
      input_filename
      note
      source_id
      test_id
      user_id
      user_name
      __typename
    }
    nextToken
    __typename
  }
}
` as GeneratedQuery<APITypes.ListTestsQueryVariables, APITypes.ListTestsQuery>;
export const listUsers = /* GraphQL */ `query ListUsers(
  $filter: ModelUserFilterInput
  $limit: Int
  $nextToken: String
  $sortDirection: ModelSortDirection
  $user_id: Int
) {
  listUsers(
    filter: $filter
    limit: $limit
    nextToken: $nextToken
    sortDirection: $sortDirection
    user_id: $user_id
  ) {
    items {
      date_of_birth
      email
      first
      last
      user_id
      __typename
    }
    nextToken
    __typename
  }
}
` as GeneratedQuery<APITypes.ListUsersQueryVariables, APITypes.ListUsersQuery>;
export const quicksightEmbed = /* GraphQL */ `query QuicksightEmbed($email: String, $visualId: String) {
  quicksightEmbed(email: $email, visualId: $visualId)
}
` as GeneratedQuery<
  APITypes.QuicksightEmbedQueryVariables,
  APITypes.QuicksightEmbedQuery
>;
blipps199 commented 1 month ago

@chrisbonifacio I was able to get rid of the error by adding the authorization here: const testSchema = generatedSqlSchema.authorization((allow) => allow.authenticated()); but the query literally returns nothing. There is definitely data. I can query it just fine from AppSync and the Amplify console. Using the front end code above.

chrisbonifacio commented 1 month ago

Hi @blipps199 ! thanks for the update. Can you check the network request that is being made from your application and share what the graphql query looks like? I'm curious to see the headers, input variables, the selection set (fields to be returned in the response), and how they compare to the AppSync console query.

blipps199 commented 1 month ago

@chrisbonifacio After doing a bit more digging I can query and get data back... but not from a list query. Only from get queries where I have to filter using a specific id for the record. I haven't tried a mutation yet but that will be soon.

blipps199 commented 1 month ago

@chrisbonifacio Here is the payload.

{"query":"query ($demographic_data_id: Int, $sortDirection: ModelSortDirection, $filter: ModelDemographic_dataFilterInput, $limit: Int, $nextToken: String) {\n listDemographic_data(\n demographic_data_id: $demographic_data_id\n sortDirection: $sortDirection\n filter: $filter\n limit: $limit\n nextToken: $nextToken\n ) {\n items {\n demographic_data_id\n user_id\n country\n state\n tribe\n height\n weight\n sex\n ethnic_group\n physical_activity_level\n smoking_history\n occupational_exposure\n previous_disease\n respiratory_medication_use\n pregnant\n }\n nextToken\n __typename\n }\n}\n","variables":{}}

When I tried to run this query through AppSync it told me sortDirection is not supported for List operations without a Sort key defined.

Once I removed that I was able to run the query. Wondering if this is what the issue is.

blipps199 commented 1 month ago

@chrisbonifacio Could this be from postgres not having a sortkey field?

chrisbonifacio commented 1 month ago

@blipps199 I'm not sure but this sounds like a potential duplicate of this other issue I was able to reproduce:

https://github.com/aws-amplify/amplify-category-api/issues/2946

chrisbonifacio commented 1 month ago

In the meantime, until we fix this bug, you should be able to use client.graphql to perform a list query with a custom graphql string.

If you want to generate graphql statements, you can use the following CLI command:

https://docs.amplify.aws/react/reference/cli-commands/#npx-ampx-generate-graphql-client-code

blipps199 commented 1 month ago

Thanks @chrisbonifacio. I was able to take my generated queries file, dupe it so I can make edits, removed sortDirection, imported the query, and use client.graphql to run the query. Its not nearly as clean but it works I suppose. Would you have any idea when this bug fix would be on the roadmap? I would really love to use client.models.model_name.list.

sundersc commented 2 weeks ago

@blipps199 Fix for https://github.com/aws-amplify/amplify-category-api/issues/2946 has been published. The next time you make a deployment, you should receive the fix automatically. Could you check if this addresses your issue?