question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

401 error when performing a Query in AppSync

See original GitHub issue

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

Authentication, GraphQL API

Amplify Categories

auth, storage, function, api

Environment information

# Put output below this line
  System:
    OS: Linux 5.11 Pop!_OS 21.04
    CPU: (8) x64 Intel(R) Core(TM) i5-8250U CPU @ 1.60GHz
    Memory: 409.40 MB / 15.52 GB
    Container: Yes
    Shell: 5.8 - /usr/bin/zsh
  Binaries:
    Node: 12.21.0 - /usr/bin/node
    Yarn: 1.22.10 - /usr/local/bin/yarn
    npm: 8.0.0 - /usr/local/bin/npm
  Browsers:
    Brave Browser: 96.1.32.106
    Chrome: 95.0.4638.69
    Firefox: 90.0
  npmPackages:
    @aws-amplify/ui-react: ^0.2.35 => 0.2.38 
    @babel/cli: ^7.15.4 => 7.15.4 
    @babel/core: ^7.15.5 => 7.15.5 (7.13.16, 7.12.3)
    @babel/preset-env: ^7.15.6 => 7.15.6 (7.13.15, 7.12.1)
    @babel/preset-typescript: ^7.15.0 => 7.15.0 (7.12.1)
    @fortawesome/fontawesome-free: ^5.15.2 => 5.15.3 
    @fortawesome/fontawesome-svg-core: ^1.2.34 => 1.2.35 
    @fortawesome/free-brands-svg-icons: ^5.15.2 => 5.15.3 
    @fortawesome/free-regular-svg-icons: ^5.15.3 => 5.15.3 
    @fortawesome/free-solid-svg-icons: ^5.15.2 => 5.15.3 
    @fortawesome/react-fontawesome: ^0.1.14 => 0.1.14 
    @material-ui/core: ^4.11.3 => 4.11.4 
    @material-ui/lab: ^4.0.0-alpha.57 => 4.0.0-alpha.58 
    @testing-library/jest-dom: ^5.11.4 => 5.12.0 
    @testing-library/react: ^11.1.0 => 11.2.6 
    @testing-library/user-event: ^12.1.10 => 12.8.3 
    @types/history: ^4.7.3 => 4.7.8 
    @types/jest: ^26.0.15 => 26.0.23 
    @types/material-ui-phone-number: ^2.2.1 => 2.2.1 
    @types/node: ^12.0.0 => 12.20.11 (15.0.1)
    @types/react: ^16.9.53 => 16.14.5 (17.0.4)
    @types/react-dom: ^16.9.8 => 16.9.12 
    @types/react-router-dom: ^5.1.7 => 5.1.7 
    @types/uuid: ^8.3.1 => 8.3.1 
    aws-amplify: ^3.3.20 => 3.3.27 
    aws-sdk: ^2.974.0 => 2.974.0 
    formik: ^2.2.6 => 2.2.6 
    geoplugin: ^1.0.5 => 1.0.5 
    graphql-tag: ^2.12.5 => 2.12.5 
    html-parse-stringify2: ^2.0.1 => 2.0.1 
    i18next: ^19.8.8 => 19.9.2 
    i18next-browser-languagedetector: ^6.0.1 => 6.1.0 
    i18next-http-backend: ^1.1.0 => 1.2.1 
    i18next-xhr-backend: ^3.2.2 => 3.2.2 
    material-ui-phone-number: ^2.2.6 => 2.2.6 
    react: ^17.0.2 => 17.0.2 (16.14.0, 15.7.0)
    react-activity: ^2.1.1 => 2.1.1 
    react-activity-indicator: ^0.7.77 => 0.7.77 
    react-dom: ^17.0.2 => 17.0.2 (16.14.0, 15.7.0)
    react-i18next: ^11.8.7 => 11.8.14 
    react-multi-carousel: ^2.6.2 => 2.6.2 
    react-native-paper: ^4.9.2 => 4.9.2 
    react-router-dom: ^5.2.0 => 5.2.0 
    react-scripts: 4.0.1 => 4.0.1 
    react-spinners: ^0.11.0 => 0.11.0 
    styled-components: ^5.2.1 => 5.2.3 
    styled-components/macro:  undefined ()
    styled-components/native:  undefined ()
    styled-components/primitives:  undefined ()
    typescript: ^4.0.3 => 4.2.4 
    uuid: ^8.3.2 => 8.3.2 (3.4.0, 3.3.2)
    web-vitals: ^0.2.4 => 0.2.4 
    yup: ^0.32.9 => 0.32.9 
    yup-phone: ^1.2.19 => 1.2.19 
  npmGlobalPackages:
    @aws-amplify/cli: 7.6.1
    depcheck: 1.4.2
    expo-cli: 4.11.0
    npm: 8.0.0
    typescript: 4.4.2
    yarn: 1.22.10

Describe the bug

To give a bit of context to our problem: we updated our Amplify framework to the version 7.6.1 but it had a lot of errors, so we decided to take down our backend and build it again.

This allowed us to fix some errors that we had prior to the update but it has now introduced a new one. Currently, we each time a user tries to access the API a 401 error is thrown.

We believe this error might be due to lack of IAM permitions but we have added the permitions that we previously had and still does not work.

Before this upgrade everything was working completly fine.

Edit:

This issue was created during a case (Case ID: 9301932391)

We tried to perform a listEvents query logged as a user in “Person” group and did not work. We were able to query it if the user is not logged in

Expected behavior

Not throw 401 error

Reproduction steps

  1. run amplify init
  2. run amplify add auth
  3. run amplify add api
  4. do a listEvents query

Code Snippet

// Put your code below this line.
const inputObject = { query: listEvents }
const response = (await API.graphql(inputObject)) as GraphQLResult<Types.ListEventsQuery>

Log output

// Put your logs below this line
{data: {…}, errors: Array(1)}
data: {listEvents: null}
errors: Array(1)
0:
data: null
errorInfo: null
errorType: "Unauthorized"
locations: [{…}]
message: "Not Authorized to access listEvents on type ModelEventConnection"
path: ['listEvents']
[[Prototype]]: Object
length: 1
[[Prototype]]: Array(0)
[[Prototype]]: Object

aws-exports.js

/* eslint-disable */ // WARNING: DO NOT EDIT. This file is automatically generated by AWS Amplify. It will be overwritten.

const awsmobile = { “aws_project_region”: “eu-west-2”, “aws_cognito_identity_pool_id”: “eu-west-2:950a8d3b-5a6c-4466-bdc8-bcfd21c1e5a9”, “aws_cognito_region”: “eu-west-2”, “aws_user_pools_id”: “eu-west-2_rqlB6WEZ2”, “aws_user_pools_web_client_id”: “7ee5vfhnub1na7ub0vb9c31h5g”, “oauth”: {}, “aws_cognito_username_attributes”: [ “EMAIL” ], “aws_cognito_social_providers”: [], “aws_cognito_signup_attributes”: [ “EMAIL”, “NAME”, “PHONE_NUMBER” ], “aws_cognito_mfa_configuration”: “OFF”, “aws_cognito_mfa_types”: [ “SMS” ], “aws_cognito_password_protection_settings”: { “passwordPolicyMinLength”: 8, “passwordPolicyCharacters”: [ “REQUIRES_LOWERCASE”, “REQUIRES_UPPERCASE”, “REQUIRES_NUMBERS” ] }, “aws_cognito_verification_mechanisms”: [ “PHONE_NUMBER” ], “aws_appsync_graphqlEndpoint”: “https://zhonadyidvaeddgqv4fy6fqb7u.appsync-api.eu-west-2.amazonaws.com/graphql”, “aws_appsync_region”: “eu-west-2”, “aws_appsync_authenticationType”: “AWS_IAM”, “aws_user_files_s3_bucket”: “outgoingawsbackend736404c325ee484e89fd39a698851231256-dev”, “aws_user_files_s3_bucket_region”: “eu-west-2” };

export default awsmobile;

Manual configuration

No response

Additional configuration

This is our schema.graphql:

"""
Description:
  * A company represents an entity that can create, delete and update events whose tickets and consumables can be sold
    trough our app.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @hasOne: allows connection to a single image
  * @manyToMany(PersonCompanyConnection): allows connecting companies to a set of partners
  * @hasMany(byCompanyByName): allows connecting to a list of groups and sort them by name
  * @hasMany(byCompanyByName): allows connecting to a set of collaborators and sort them by their name
  * @hasMany(byCompanyByDate): allows connecting to a set of events and sort them by date

Arguments:
  * id: company's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: name of the company
  * email: company's email -> used for contact purposes
  * iban: holds bank account number which will receive payments after an event is over
  * consumablesFee: percentage of fee taken by us from this company for their consumable transactions
  * ticketsFee: percentage of fee taken by us from this company for their tickets transactions
  * avatar: logo/icon of the company -> if is null, then we should show the default image on the frontend
  * initials: company's short name or abreviation
  * associates: list of common user that are partners of this company
  * groups: holds a list of groups held by this company
  * collaborators: holds a list of all collaborators
  * events: list of upcoming events provided by the company
"""
type Company @model

  @auth(rules:[
    # Allows the owner of this object to update and delete info
    { allow: owner, ownerField: "owner", operations: [update, delete, read] },
    # Only those who belong to this group can create a table for themselves
    { allow: groups, groups: ["Company"], operations: [create, read] },
    # Anyone without credentials can read this object
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  email:                  AWSEmail !
  iban:                   String !
  initials:               String !
  consumablesFee:         Float !
  ticketsFee:             Float !
  avatar:                 Image                              @hasOne
  associates:             [Person !] !                       @manyToMany(relationName: "PersonCompanyConnection")
  groups:                 [Group !] !                        @hasMany(indexName: "byCompanyByName", fields: ["id"])
  collaborators:          [Collaborator !] !                 @hasMany(indexName: "byCompanyByName", fields: ["id"])
  events:                 [Event !] !                        @hasMany(indexName: "byCompanyByDate", fields: ["id"])

}


"""
Description:
  * Represents a person that works for a specific company.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byCompanyByName): allows connecting to a company and sort all company's collaborators by name
  * @index(byGroupByName): allows connecting to a group and sort all group's collaborators by name
  * @hasOne: allows connection to a single image
  * @hasMany(byCollaboratorByEvent): creates link between an collaborator and a set of transactions. Sorts by event
  * @manyToMany(CollaboratorEventConnection): connects multiple collaborators to multiple events

Arguments:
  * id: collaborator's identity descriptor -> UUID provided by user object upon login
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: person's name
  * email: collaborator's email
  * phone: collaborator's phone number
  * activity: description of the collaborator's work
  * companyID: company for which this person works
  * groupID: group for which this collaborator belongs -> can be null which means that he does not have any group
  * avatar: image representing the user -> if is null, then we should show the default image on the frontend
  * history: lits of previous transactions
  * events: list holding all the assigned events to this person
"""
type Collaborator @model

  @auth(rules:[
    # Allows Companies can read, update and delete their collaborators
    { allow: owner, ownerField: "owner", operations: [update, delete, read] },
    # Only Company accounts can create a set of collaborators and list them
    { allow: groups, groups: ["Company"], operations: [create] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  email:                  AWSEmail !
  phone:                  AWSPhone !
  activity:               String
  companyID:              ID !                               @index(name: "byCompanyByName", sortKeyFields: ["name"], queryField: "collaboratorByCompany")
  groupID:                ID                                 @index(name: "byGroupByName", sortKeyFields: ["name"], queryField: "collaboratorByGroupByName")
  avatar:                 Image                              @hasOne
  history:                [ConsumableTransaction !] !        @hasMany(indexName: "byCollaboratorByEvent", fields: ["id"])
  events:                 [Event !] !                        @manyToMany(relationName: "CollaboratorEventConnection")

}


"""
Description:
  * Represents a group/stall in a party. Collaborators and consumables can be associated to a stall. Is basically a group of
    collaborators.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byCompanyByName): allows connecting to a company and sort by group's id
  * @hasMany(byGroupByName): allows connecting to a set of collaborators and sorts by name
  * @hasMany(byGroupByPrice): allows connecting to a list of consumables and sorts them by price

Arguments:
  * id: group's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: group's name
  * companyID: company that hold this group
  * collaborators: list of collaborators belonging to this group
  * consumables: list of consumables sold by this group
"""
type Group @model

  @auth(rules:[
    # Only the company that created this group can update and delete them
    { allow: owner, ownerField: "owner", operations: [update, delete, read] },
    # Only a Company can create a group
    { allow: groups, groups: ["Company"], operations: [create] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  companyID:              ID !                               @index(name: "byCompanyByName" , sortKeyFields: ["name"], queryField: "groupsByCompany")
  collaborators:          [Collaborator !] !                 @hasMany(indexName: "byGroupByName", fields: ["id"])
  consumables:            [Consumable !] !                   @hasMany(indexName: "byGroupByPrice", fields: ["id"])

}


"""
Description:
  * Describes an event created by a Company and that can have a lot of attendees. Also allows the upload of images,
    a description of the event and the creation of consumables entities to be purchased during the event.

Directives:
  * @model: Creates an object in our database
  * @searchable: Allows complex searches on this object using a dynamodb stream and open search service from aws
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byCompanyByDate): allows connecting to a company and sort all company's events by their date
  * @hasOne: allows connecting an event to a banner (main image)
  * @hasOne: allows connecting an event to a poster
  * @hasMany(byEvent4): allows connecting a set of streaks to an event
  * @hasMany(byEvent3): allows connecting a set of images to an event
  * @hasMany(byEventByPrice2): connects multiple events to multiple tickets
  * @manyToMany(CollaboratorEventConnection): connects multiple events to multiple collaborators
  * @hasMany(byEventByPrice): allows connecting an event with the sold consumables and sort them by price
  * @hasMany(byEventByDate1): allows linking a set of ticket purchases with an event and sort by date
  * @hasMany(byEventByDate2): allows linking a set of consumable purchases with an event and sort by date

Arguments:
  * id: event's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: name of the event
  * maxNumberOfPeople: maximum number of attendees -> can be null (doesn't have a limit)
  * noEntriesDatetime: datetime after which nobody else will enter the event -> can be null (doesn't have a restriction)
  * isPromoted: tells if the owner is paying to put it in the frontpage of the app
  * location: exact location where the event will be held (long, lat)
  * addressAlias: simple reference to the place of the event
  * companyID: company which holds this event -> used to link both entities
  * description: short description that is meant to captive more people -> written by the event provider
  * startDate: event start datetime -> used mainly for sorting and filters
  * endDate: event end datetime -> used mainly for sorting and filters
  * date: date span of occurrence in "Javascript Date" -> ISO format 8601
  * banner: photo that is shown in the frontpage -> all events should have one (even little ones)
  * poster: list of artists that are going to perform in the event
  * streaks: list of consumable streaks
  * photos: lits of photos associated with this event (menu, singers, ect...) -> is a set of Images
  * tickets: list of tickets being sold for this event
  * collaborators: list of collaborators that are going to be serving in this event
  * transactionsTickets: list of ticket transactions in this event
  * transactionsConsumables: list of consumables transactions in this event
"""
type Event @model # TODO: @searchable is currently disabled because of an issue (Check Amplify GitHub)

  @auth(rules:[
    # Only the company that created this event can update/delete it
    { allow: owner, ownerField: "owner", operations: [update, delete] },
    # Only a Company can create an event
    { allow: groups, groups: ["Company"], operations: [create] },
    # Anyone can read info from the event
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  maxNumberOfPeople:      Int
  noEntriesDatetime:      AWSDateTime
  isPromoted:             Boolean !
  location:               Location !
  addressAlias:           String !
  companyID:              ID !                              @index(name: "byCompanyByDate", sortKeyFields: ["startDate"], queryField: "eventsByCompanyByDate")
  description:            String !
  startDate:              AWSDateTime !
  endDate:                AWSDateTime !
  date:                   [[AWSDateTime !] !] !
  nrOfPeopleGoing:        Int !
  banner:                 Image !                            @hasOne
  poster:                 Poster                             @hasOne
  streaks:                [Streak !] !                       @hasMany(indexName: "byEvent4", fields: ["id"])
  photos:                 [Image ! ] !                       @hasMany(indexName: "byEvent3", fields: ["id"])
  tickets:                [Ticket !] !                       @hasMany(indexName: "byEventByPrice2", fields: ["id"])
  collaborators:          [Collaborator  !] !                @manyToMany(relationName: "CollaboratorEventConnection")
  consumables:            [Consumable !] !                   @hasMany(indexName: "byEventByPrice", fields:["id"])
  transactionsTickets:    [TicketTransaction !] !            @hasMany(indexName: "byEventByDate1", fields: ["id"])
  transactionsConsumables:[ConsumableTransaction !] !        @hasMany(indexName: "byEventByDate2", fields: ["id"])

}


"""
Description:
  * This object is used to categorise a ticket for an event. A ticket can have multiple days, prices and can have
    been already read or not. Can also change its price according to the day

Directives:
  * @model: Creates an object in our database -> Doesn't need queries because we can access trough both objects
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byEventByPrice2): allows connecting to an event and sorts by price

Arguments:
  * id: ticket's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * stock: number of tickets available -> can be null (does not have limit)
  * price: normal selling price
  * memberPrice: company associate selling price. Can be null
  * priceOverRideDate: date after which the official price will be changed
  * overridePrice: normal selling price that becomes active after a certain date. Can be null
  * memberOverridePrice: company associate selling price that becomes active after a certain date. Can be null
  * validDates: 2 element array which symbols a time span in which this ticket is valid
  * eventID: event which holds this ticket
"""
type Ticket @model

  @auth(rules:[
    # Only the company that created this ticket can update/delete it
    { allow: owner, ownerField: "owner", operations: [update, delete] },
    # Only a Company can create a ticket
    { allow: groups, groups: ["Company"], operations: [create] },
    # Anyone can read info from the ticket
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  stock:                  Int
  price:                  Float !
  memberPrice:            Float
  priceOverRideDate:      AWSDateTime
  overridePrice:          Float
  memberOverridePrice:    Float
  validDates:             [[AWSDateTime !] !] !
  eventID:                ID !                               @index(name: "byEventByPrice2", sortKeyFields: ["price"], queryField: "ticketsByEventByPrice")

}


"""
Description:
  * This object holds the event's poster. The poster has all the artists that are going to perform and the time at
    which they will do it.

Directives:
  * @model: Creates an object in our database -> Doesn't need queries because we can access trough both objects
  * @auth: Specifies who can access this object and what operations they can perform

Arguments:
  * id: poster's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * artists: set of artists -> each index maps to a day of the event and the inner index maps to an artist of that day.
if the outer field is null then, that day does not have any performances
"""
type Poster @model

  @auth(rules:[
    # Only the company that created this poster can update/delete it
    { allow: owner, ownerField: "owner", operations: [update, delete] },
    # Only a Company can create a poster
    { allow: groups, groups: ["Company"], operations: [create] },
    # Anyone can read info from the poster
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  artists:                [[Artist !] ] !

}


"""
Description:
  * Represents a purchase of tickets in our database. A user can purchase tickets from the app or from the website.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byPersonByDate1): allows linking a set of transactions with a person and sorts them by date
  * @index(byEventByDate1): allows linking a set of transactions with an event and sorts them by date
  * @hasOne: allows linking a ticket with a purchase made by the user

Arguments:
  * id: transaction's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * date: date of transaction
  * price: amount of money payed for the ticket
  * personID: id of the person buying this ticket
  * eventID: id of ticket's event
  * ticket: ticket that was bought
"""
type TicketTransaction @model

  @auth(rules: [
    # Only a Person can buy a ticket
    { allow: groups, groups: ["Person"], operations: [create] },
    # Only the owner can update or delete the transaction
    { allow: owner, ownerField: "owner", operations: [delete, update] },
    # Anyone can see that a person has a ticket for this event
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  date:                   AWSDateTime !
  price:                  Float !
  personID:               ID !                               @index(name: "byPersonByDate1", sortKeyFields: ["date"], queryField: "ticketTransactionByPerson")
  eventID:                ID !                               @index(name: "byEventByDate1", sortKeyFields: ["date"], queryField: "ticketTransactionByEvent")
  ticket:                 Ticket !                           @hasOne

}


"""
Description:
  * Represents a transaction made with our payment affiliates to a user.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byPerson4): allows linking a set of transactions with a person and sorts them by date

Arguments:
  * id: transaction's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * date: date of transaction
  * amount: amount of money put in the wallet
  * method: which payment method was used
  * status: transaction current status
  * transactionID: id of the transaction with our payment provider
  * personID: id of the person buying this ticket
"""
type WalletTransaction @model

  @auth(rules: [
    # Only a Person can put money in their wallet
    { allow: groups, groups: ["Person"], operations: [create] },
    # Only the owner can manage the transaction
    { allow: owner, ownerField: "owner", operations: [delete, update, read] },
  ]) {

  id:                     ID !
  owner:                  String !
  date:                   AWSDateTime !
  amount:                 Float !
  method:                 PaymentMethod
  status:                 PaymentStatus !
  transactionID:          String !
  personID:               ID !                               @index(name: "byPerson4", sortKeyFields: ["date"])

}


"""
Description:
  * Represents a consumable transaction in our database. A collaborator has to read a QR from a person's phone and
    register a transaction holding a specific amount of a set of products.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byEventByDate2): allows linking a transaction with an event and sort it by date
  * @index(byPersonByDate2): allows linking a transaction with a person and sorts them by date
  * @index(byCollaboratorByEvent): creates link between a transaction and an collaborator. Sorts by event
  * @hasMany(byTransaction): allows linking a transaction with a set of consumables + amount

Arguments:
  * id: transaction's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email (in this case, the Person's)
  * date: date of transaction
  * price: amount payed for this transaction
  * collaboratorID: id of the collaborator that performed this transaction
  * personID: id of the person buying this items
  * eventID: id of the event in which this transaction took place
  * cart: cart that holds
"""
type ConsumableTransaction @model

  @auth(rules: [
    # The person that payed for this transaction can only read it
    { allow: owner, ownerField: "owner", operations: [read] },
    # Only collaborators can create, delete and update transactions
    { allow: groups, groups: ["Collaborator"], operations: [create, delete, update, read] }
  ]) {

  id:                     ID !
  owner:                  String !
  date:                   AWSDateTime !
  price:                  Float !
  collaboratorID:         ID !                               @index(name: "byCollaboratorByEvent", sortKeyFields: ["eventID"], queryField: "consumableTransactionByCollaborator")
  personID:               ID !                               @index(name: "byPersonByDate2", sortKeyFields: ["date"], queryField: "consumableTransactionByPerson")
  eventID:                ID !                               @index(name: "byEventByDate2" , sortKeyFields: ["date"], queryField: "consumableTransactionByEvent")
  cart:                   [PairTransaction !] !              @hasMany(indexName: "byTransaction", fields: ["id"])

}


"""
Description:
  * Represents a ticket held by a user. Holds a reference to the type of ticket and if it has been read or not.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byPerson2): allows linking a person with a set of tickets
  * @hasOne: links a personTicket with a ticket
  * @hasOne(personID): links a personTicket with a person

Arguments:
  * id: person's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * readDate: holds the date of reading of this ticket -> if was not read, is the start of ISO format date
  * eventID: event's id which this ticket belongs to
  * personID: person ID that holds this ticket -> mainly used for the connection
  * ownerPhone: ticket's owner's phone number -> used for queries through his contacts
  * visibility: holds user's privacy policy -> mainly used for fetching users in one query
  * ticket: event ticket
  * person: person object that holds this ticket
"""
type PersonTicket @model

  @auth(rules: [
    # Only a person in Persons can buy tickets
    { allow: groups, groups: ["Person"], operations: [create] },
    # The owner can update/delete their info
    { allow: owner, ownerField: "owner", operations: [update, delete, read] },
    # Anyone can see which events another is going to attend
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  readDate:               AWSDateTime !
  eventID:                ID !
  personID:               ID !                               @index(name: "byPerson2", queryField: "personTicketByPerson")
  ownerPhone:             AWSPhone !
  visibility:             Privacy !
  ticket:                 Ticket !                           @hasOne
  person:                 Person !                           @hasOne(fields: ["personID"])

}


"""
Description:
  * Represents a common user. Participates in events and can purchase consumables. Also holds a wallet.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @hasOne: links a single profile picture with a user
  * @manyToMany(PersonCompanyConnection): connects people to a set of companies
  * @hasMany(byPerson1): connects one person to a set of consumables
  * @hasMany(byPerson2): connects one person to a set of event tickets
  * @hasMany(byPersonByDate1): allows linking a set of ticket transactions with a person and sorts by date
  * @hasMany(byPersonByDate2): allows linking a set of consumable transactions with a person and sorts by date
  * @hasMany(byPerson4): allows linking a set of wallet transactions with a person and sorts by date

Arguments:
  * id: person's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: person's name
  * email: person's email
  * phone: person's phone number
  * dateOfBirth: user's date of birth
  * university: person's university
  * wallet: amount of money available for in-app purchases
  * privacy: user's privacy definition
  * currentEventID: event's ID which this person is currently in -> if is null, then the user is not in a party
  * avatar: user's profile picture -> if is null, then we should show the default image on the frontend
  * memberships: set of companyID's which the user is a member/associate/partner of
  * consumables: list of consumables + amount that this user has bought
  * tickets: list of events which this user will attend
  * historyTicket: list of past ticket purchases
  * historyConsumables: list of past consumables purchases
  * historyWallet: list of past wallet transactions
"""
type Person @model

  @auth(rules: [
    # Only a person in Persons can create a reference to itself
    { allow: groups, groups: ["Person"], operations: [create] },
    # The owner can update/delete their info
    { allow: owner, ownerField: "owner", operations: [update, delete, read] },
    # An collaborator can update and read a user's info
    { allow: groups, groups: ["Collaborator"], operations: [update, read] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  email:                  AWSEmail !
  phone:                  AWSPhone !
  dateOfBirth:            AWSDateTime !
  university:             String
  wallet:                 Float !
  privacy:                Privacy !
  currentEventID:         ID
  avatar:                 Image                              @hasOne
  memberships:            [Company !] !                      @manyToMany(relationName: "PersonCompanyConnection")
  consumables:            [PairPerson !] !                   @hasMany(indexName: "byPerson1", fields: ["id"])
  tickets:                [PersonTicket !] !                 @hasMany(indexName: "byPerson2", fields: ["id"])
  historyTickets:         [TicketTransaction !] !            @hasMany(indexName: "byPersonByDate1", fields: ["id"])
  historyConsumables:     [ConsumableTransaction !] !        @hasMany(indexName: "byPersonByDate2", fields: ["id"])
  historyWallet:          [WalletTransaction !] !            @hasMany(indexName: "byPerson4", fields: ["id"])

}


"""
Description:
  * Represents a consumable inside our app (beer, hot dog, shots, etc...).

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byEventByPrice): allows connecting to an event and sorts all consumables by their price
  * @index(byGroupByPrice): allows connecting to an group and sorts all consumables by their price
  * @hasOne: allows connecting to a larger version of this consumable

Arguments:
  * id: consumable's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * type: is either a drink or a food item
  * size: consumables can have different sizes
  * name: consumable's name
  * price: normal selling price
  * overridePrice: normal selling price that becomes active after a certain date. Can be null
  * memberPrice: company associate selling price. Can be null
  * memberOverridePrice: company associate selling price that becomes active after a certain date. Can be null
  * priceOverrideStartDate: date after which the official price will be changed
  * priceOverrideEndDate: date after which the official price will be changed to the regular one
  * eventID: holds event's id from which this consumable is from
  * groupID: holds group's id which sells this consumable. If is null, then it is not associated with any group
  * largeConsumable: larger version of this consumable -> if is null, then there is none
"""
type Consumable @model

  @auth(rules: [
    # Only a company can create a consumable for an event
    { allow: groups, groups: ["Company"], operations: [create] },
    # Only the company that created the consumable can update and delete it's info
    { allow: owner, ownerField: "owner", operations: [update, delete] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  type:                   ConsumableType !
  size:                   ConsumableSize !
  name:                   String !
  price:                  Float !
  overridePrice:          Float
  memberPrice:            Float
  memberOverridePrice:    Float
  priceOverrideStartDate: AWSDateTime
  priceOverrideEndDate:   AWSDateTime
  eventID:                ID !                               @index(name: "byEventByPrice" , sortKeyFields: ["price"], queryField: "consumablesByEvent")
  groupID:                ID                                 @index(name: "byGroupByPrice", sortKeyFields: ["price"], queryField: "consumablesByGroup")
  largeConsumable:        Consumable                         @hasOne

}


"""
Description:
  * Represents a streak of consumables inside our app.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byEvent4): allows connecting to an event and sorts all consumables by their name
  * @hasOne: allows connecting a streak with a prize/promo
  * @hasMany(byStreak): allows connecting a streak with a set of consumables that the user needs to buy

Arguments:
  * id: streak's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * name: streak's name
  * eventID: id of the event that holds this consumables streak
  * promoPrice: price of the pair in promotion
  * promoPair: pair of consumable + amount that is given to the user as a promotion/reward
  * set: group of consumables + amount that the user needs to buy
"""
type Streak @model

  @auth(rules: [
    # Only a company can create a streak for an event
    { allow: groups, groups: ["Company"], operations: [create] },
    # Only the company that created the streak can update and delete it's info
    { allow: owner, ownerField: "owner", operations: [update, delete] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  name:                   String !
  eventID:                ID !                               @index(name: "byEvent4", sortKeyFields: ["name"], queryField: "streakByEvent")
  promoPrice:             Float !
  promoPair:              PairStreak !                       @hasOne
  set:                    [PairStreak !] !                   @hasMany(indexName: "byStreak", fields: ["id"])

}


"""
Description:
  * Represents an Image inside our app.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byEvent3): allows connecting an image to an event

Arguments:
  * id: image's identity descriptor
  * owner: reference to the owner of this object -> is the same as the owner's email
  * eventID: images can be related to events, so we need to store their ID -> can be null if it is not an event's image
  * filename: path to the image in S3
  * identityID: corresponds to the cognito identity identification and we need it to find the folder of this image
"""
type Image @model

  @auth(rules: [
    # Anyone can create an image and the owner can interact with it in any way
    { allow: owner, ownerField: "owner", operations: [create, update, delete, read] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  owner:                  String !
  eventID:                ID                                 @index(name: "byEvent3", queryField: "imageByEvent")
  filename:               String !
  identityID:             String !

}


"""
Description:
  * Represents a consumable and an amount related to it. Can only be related to a Person.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byPerson1): allows connecting set of pairs to a person
  * @hasOne: Allows connecting to a single consumable

Arguments:
  * id: pair's identity descriptor
  * personID: associated person's id
  * whoGaveIt: name of person/enterprise that gave this consumable
  * amount: consumable's amount
  * consumable: consumable's of this pair
"""
type PairPerson @model

  @auth(rules: [
    # Anyone can create a pair and the owner can interact with it in any way
    { allow: owner, ownerField: "owner", operations: [create, update, delete, read] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  personID:               ID !                               @index(name: "byPerson1", sortKeyFields: ["amount"], queryField: "pairsByPersonByAmount")
  whoGaveIt:              String !
  amount:                 Int !
  consumable:             Consumable !                       @hasOne

}


"""
Description:
  * Represents a consumable and an amount related to it. Can only be related to a Streak.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byStreak): allows connecting set of pairs to a streak
  * @hasOne: Allows connecting to a single consumable

Arguments:
  * id: pair's identity descriptor
  * streakID: associated streak's id
  * amount: consumable's amount
  * consumable: consumable's of this pair
"""
type PairStreak @model

  @auth(rules: [
    # Anyone can create a pair and the owner can interact with it in any way
    { allow: owner, ownerField: "owner", operations: [create, update, delete, read] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  streakID:               ID !                               @index(name: "byStreak", queryField: "pairStreakByStreak")
  amount:                 Int !
  consumable:             Consumable !                       @hasOne

}


"""
Description:
  * Represents a consumable and an amount related to it. Can only be related to a ConsumableTransaction.

Directives:
  * @model: Creates an object in our database
  * @auth: Specifies who can access this object and what operations they can perform
  * @index(byTransaction): allows connecting set of pairs to a transaction
  * @hasOne: Allows connecting to a single consumable

Arguments:
  * id: pair's identity descriptor
  * transactionID: associated transaction's id
  * amount: consumable's amount
  * wasUsedInPromo: signals that this transaction was made as part of a streak's promo
  * streakID: holds streak id if this transaction was used as part of a streak's promo
  * wasPersonConsumable: signals that this transaction was made with person's token consumables
  * whoGaveIt: holds person/enterprise name that gave the user this token consumable
  * consumable: consumable's of this pair
"""
type PairTransaction @model

  @auth(rules: [
    # Anyone can create a pair and the owner can interact with it in any way
    { allow: owner, ownerField: "owner", operations: [create, update, delete, read] },
    # Anyone can read this info
    { allow: public, provider: iam, operations: [read] }
  ]) {

  id:                     ID !
  transactionID:          ID !                               @index(name: "byTransaction", queryField: "pairTransactionByTransaction")
  amount:                 Int !
  wasUsedInPromo:         Boolean !
  streakID:               ID
  wasPersonConsumable:    Boolean !
  whoGaveIt:              String
  consumable:             Consumable !                       @hasOne

}


"""
Description:
  * Represents an artist and the datetime at which he will perform.

Arguments:
  * name: artist's name
  * performanceDateTime: date at which he will perform
"""
type Artist {

  name:                   String !
  performanceDateTime:    AWSDateTime !

}


"""
Description:
  * Represents a coordinates system to locate an object in the globe.

Arguments:
  * lon: longitude floating point coordinate value
  * lat: latitude floating point coordinate value
"""
type Location {

  lon:                    Float !
  lat:                    Float !

}


"""
Description:
  * Is the default return type from our serverless functions.

Arguments:
  * statusCode: html code that represents an action
  * headers: CORS headers
  * body: can contain an error message or a success message
  * status: tells us if the function was successful or not
"""
type Response {

  statusCode:             Int
  headers:                AWSJSON
  body:                   AWSJSON
  status:                 Status

}


"""
Description:
  * Is the default return type for our nearby events call.

Arguments:
  * items: list of gathered events
  * total: number of events returned
  * nextToken: is used for pagination
"""
type ModelNearbyEventConnection {

  items:                  [Event]
  total:                  Int
  nextToken:              String

}


"""
Description:
  * Our consumables can be either a drink  or food (hot dogs, etc.). We use this distinction so that
    we can have multiple tabs.

Arguments:
  * FOOD: foods such as hot dogs, etc...
  * SPIRITS: vodkas, whiskey, etc...
  * SHOTS: shots and mixtures, etc...
  * BEER: beer related drinks, etc...
  * SANGRIA: sangria, etc...
  * CIDER: cider, etc...
  * NONALCOHOLIC: sodas, water, etc...
"""
enum ConsumableType {

  BEER
  SANGRIA
  CIDER
  SPIRITS
  SHOTS
  FOOD
  NONALCOHOLIC

}


"""
Description:
  * When a user adds money to their wallet, they can use one of the following methods.

Arguments:
  * MBWAY: made a transactions with mbway
  * CREDITCARD: made a transaction with a credit card
"""
enum PaymentMethod {

  MBWAY
  CREDITCARD

}


"""
Description:
  * The payment requested by a user when he adds fund to their wallet can have one of the following states.

Arguments:
  * PENDING: is still being proccessed
  * APPROVED: is done and was approved
  * DENIED: is done and was denied
"""
enum PaymentStatus {

  PENDING
  APPROVED
  DENIED

}


"""
Description:
  * Our consumables can have differente sizes and both have different values for leaderboard purposes.

Arguments:
  * REGULAR: regular/normal consumable size
  * LARGE: large consumable size
"""
enum ConsumableSize {

  REGULAR
  LARGE

}


"""
Description:
  * Lambda return status.

Arguments:
  * SUCCESS: lambda was successful
  * FAILED: lambda was not successful
"""
enum Status {

  SUCCESS
  FAILED

}


"""
Description:
  * Holds the type of visibility that our users wants.

Arguments:
  * PRIVATE: no one can see this info besides the owner
  * PROTECTED: only our friends can see this info
  * PUBLIC: anyone can see this info
"""
enum Privacy {

  PRIVATE
  PROTECTED
  PUBLIC

}


"""
Description:
  * Represents a consumable + amount. Is only used in ConsumablePurchase lambda call.

Arguments:
  * consumableID: id of the consumable thats is being bought
  * amount: amount of this consumable that is being bought
"""
input ConsumableAmount {

  consumableID:           String !
  amount:                 Int !

}


"""
Description:
  * Used in TicketPurchase lambda call.

Arguments:
  * personID: id of the user which is purchasing this ticket
  * ticketID: id of ticket that is being bought
"""
input TicketPurchaseInput {

  personID:               String !
  ticketID:               String !

}


"""
Description:
  * Used in ConsumablePurchase lambda call.

Arguments:
  * personID: id of the user which is purchasing this set of consumables
  * collaboratorID: id of collaborator that is performing this transaction
  * consumables: set of consumableID + amount that is being bought
"""
input ConsumablePurchaseInput {

  personID:               String !
  collaboratorID:         String !
  consumables:            [ConsumableAmount !] !

}


"""
Description:
  * Used in HasTicket lambda call.

Arguments:
  * personID: id of the user which is entering the input event
  * eventID: id of event which the user is trying to enter
"""
input HasTicketInput {

  personID:               ID !
  eventID:                ID !

}


"""
Description:
  * Used in ScannerEncryption lambda call.

Arguments:
  * info: string that is going to be encrypted
"""
input ScannerEncryptionInput {

  info:                   String !

}


"""
Description:
  * Used in ScannerDecryption lambda call.

Arguments:
  * info: string that is going to be decrypted
"""
input ScannerDecryptionInput {

  info:                   String !

}


"""
Description:
  * Used in DeleteUser lambda call.

Arguments:
  * username: user's username whose account will be deleted
"""
input DeleteUserInput {

  username:               ID !

}


"""
Description:
  * Used in SendConsumable lambda call.

Arguments:
  * personID: user's username
  * phone: phone number of the user that we are sending a consumable to
  * consumable: consumable + amount pair that is being sent
"""
input SendConsumableInput {

  personID:               ID!
  phone:                  AWSPhone!
  consumable:             ConsumableAmount!

}


"""
Description:
  * Used in CheckoutMBWay lambda call.

Arguments:
  * value: value to be payed
"""
input CheckoutMBWayInput {

  value:                   Float!

}


"""
Description:
  * Used in FetchEventStatistics lambda call.

Arguments:
  * eventID: id of the event
"""
input FetchEventStatisticsInput {

  eventID:                ID!

}


"""
Description:
  * Used in geo-spatial query for events.

Arguments:
  * lon: longitude floating point coordinate value
  * lat: latitude floating point coordinate value
"""
input LocationInput {

  lon:                    Float !
  lat:                    Float !

}


"""
Description:
  * Holds all our open search service calls

Arguments:
  * nearbyEvents -> fetches the nearest events to the user or specific location like a city
"""
type Query {

  nearbyEvents (
    location:             LocationInput !,                   # Coordinates
    endDate:              AWSDateTime !,                     # Event end date (used to filter results)
    m:                    Int,                               # Maximum radius to search for in km
    limit:                Int,                               # Number of requested entries
    nextToken:            String                             # Pagination token
  ) : ModelNearbyEventConnection

}


"""
Description:
  * Holds all our lambda functions.

Arguments:
  * ticketPurchase -> purchases a ticket for a user
  * consumablePurchase -> purchases a consumable for a user
  * hasTicket -> checks if a user has a valid ticket an event
  * deleteUser -> delete input user
  * scannerEncryption -> encrypts user's username
  * scannerDecryption -> decrypts user's username
"""
type Mutation {

  ticketPurchase(input: TicketPurchaseInput !): Response
    @function(name: "doTicketPurchase-${env}")

  consumablePurchase(input: ConsumablePurchaseInput !): Response
    @function(name: "doConsumablePurchase-${env}")

  hasTicket(input: HasTicketInput !): Response
    @function(name: "doHasTicket-${env}")

  deleteUser(input: DeleteUserInput !): Response
    @function(name: "doAuthDeleteUser-${env}")

  scannerEncryption(input: ScannerEncryptionInput !): String
    @function(name: "doUserEncryptionCall-${env}")

  scannerDecryption(input: ScannerDecryptionInput !): String
    @function(name: "doUserDecryptionCall-${env}")

  sendConsumable(input: SendConsumableInput!): Response
    @function(name: "doSendConsumable-${env}")

  checkoutMBway(input: CheckoutMBWayInput!): Response
    @function(name: "coCheckoutMBWay-${env}")

  fetchEventStatistics(input: FetchEventStatisticsInput!): Response
    @function(name: "doFetchEventStatistics-${env}")

}


"""
Description:
  * Holds all our subscriptions.

Arguments:
  * onPersonUpdate -> updates client app when an update happens on a specific user
"""
type Subscription {

  onPersonUpdate(id: ID!): Person
    @aws_subscribe(mutations: ["updatePerson", "createPerson"])

  onCollaboratorUpdate(companyID: ID!): Collaborator
    @aws_subscribe(mutations: ["createCollaborator", "updateCollaborator"])

}

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:6

github_iconTop GitHub Comments

1reaction
Diogo-Vcommented, Dec 9, 2021

@adilusman51 I will keep an eye on that pull request and wait for it to be merged befored trying to deploy our @searchable tag.

Thank you very much for warning us before wasting a week on that.

1reaction
Diogo-Vcommented, Dec 5, 2021

Issue has been solved. Due to the new update, all @auth deny every request by default and we just had to update the schema’s @auth tags to reflect those changes

Read more comments on GitHub >

github_iconTop Results From Across the Web

Error 401 Unauthorized when trying to connect to appsync ...
I am using the <amplify-authenticator> component for logging in; I am using the <amplify-connect> component to run the graphql query.
Read more >
Appsync return 401 errors when connecting with cognito
Appsync works great from the console, but when i make any requests from iOS i get a 401 error without any error messages....
Read more >
AWS AppSync Error: Request failed with status code 401
Click it and then log in using a Cognito user. The AppSync console will them use the token from that Cognito login to...
Read more >
Multiple Authorization methods in a single GraphQL API with ...
I'd need an API with API Keys for public access without the email field in the Notes type and a duplicated API with...
Read more >
Handling operation errors - Apollo GraphQL Docs
Apollo Client can encounter a variety of errors when executing operations on your ... Validation errors (e.g., a query included a schema field...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found