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

Request for improved schema.graphql documentation and schema verfication output #319

Open JessicaMulein opened 4 years ago

JessicaMulein commented 4 years ago

Note: If your feature-request is regarding the AWS Amplify Console service, please log it in the official AWS Amplify Console forum

Is your feature request related to a problem? Please describe. It isn't a problem per-se, but when my schema has an issue with a connection, the debug output is really more relevant to the amplify cli app itself and tells very little about where the problem it's talking about is located. If it's something obvious about a type "type Blah" somewhere in it, you get lucky- but mysterious fields on null, and other errors are tough to locate, especially if you're making the same mistake due to a lack of understanding in a bunch of places and you can't tell if you've squashed one or not.

This also leads me to requesting some additional documentation clarification and examples in the way of a slightly more complex schema definition i'd shove straight into my schema.graphql.

Some additional insight into knowing how to get intellij to be aware of the directives you've created would be appreciated. It was very difficult to understand at first which things were stuff you had in the background that would magically work despite the red in my IDE, that I didn't need to define some of the things.. and it was difficult to figure out which defines I do in fact need. It also took a minute to figure out things like "owner" are one of those enums I don't really have quick access to without searching around in graphql documentation.

the learning curve for someone totally new to graphql and amplify was tough for me as I can't tell what's stuff specific to you that I need to look up in your documentation (or what's missing) and what's in the vanilla graphql basics. I ran my schema past a guy who knew graphql but not amplify and got no additional insights.

I've blown a couple of days iterating, which amounts to changing a line or two, tabbing back to the command line, running codegen and modelgen and trying to divine the problem and iterate.

Describe the solution you'd like Could I humbly request that you add some more extensive examples and documentation on the extensions/directives in GraphQL- particularly more advanced connections- and improve the debug output of modelgen to explain where it fails with connections/keys weird errors.

Would like to see a little bit more detailed example with pivot tables and cognito/LWA auth.

Mainly though- the debug output is impossible to decipher. null on fields? where? NO idea. even if i succeed and get everything to compile models, then I get a dynamo 1-1 key error which doesn't make sense for my schema, but can never figure out what type or line is blowing up

It's hard to know whether types like AWSTimestamp are things I should be using, or letting amplify expand.

Giving additional insight to the IntelliJ setup (it was miraculous I even discovered graphqlconfig yaml) would be really helpful. I think I have all the right plugins.

Standby for additional comment with schema example

JessicaMulein commented 4 years ago

I'm not keen on giving my schema exactly, so I'm going to paste it and edit some names to obfuscate my project a little- it may introduce typos that aren't really part of my "bug". FYI/YMMV/FWIW

Much of this was through hard earned documentation scrubbing and googling/duckducking (gooseing? "duck duck go(ose)?".. I may have the auth stuff totally wrong- but I was trying to get it close enough.. I originally had the cognito stuff attached to a different model..

I think this is the gist of what I was trying to do in terms of links of things. Some of the original intent is purposefully lost, and it may not make sense why I thought I needed a particular type.. but nevertheless, here's what's left. I did remove a couple types and associated connections, hopefully leaving everything syntactically correct(to where I had it).. but I haven't specifically tried this particular config. Forgive some casing issues introduced by search/replace to change concept names.

schema {
  query: QueryRoot
}

type Badge @model
  @key(fields: ["id"])
  @key(name: "badgesByCreatorID", fields: ["creatorID", "id"])
  @key(name: "badgesByTitle", fields: ["title", "id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  title: String!
  description: String!
  enabled: Boolean!
  visible: Boolean!
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type EarnedBadges @model
  @key(fields: ["id"])
  @key(name: "earnedBadgesByBadgeID", fields: ["badgeID"])
  @key(name: "earnedBadgesByEntityID", fields: ["entityID"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  badgeID: ID!
  badge: Badge! @connection(fields: ["badgeID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

enum EmailType {
  WORK
  HOME
  PERSONAL
  BUSINESS
}

enum PrimaryType {
  PRIMARY
  OTHER
}

type AssociatedEntityEmailAddress @model
@auth(rules: [
  { allow: public, operations: [read] }
])
{
  id: ID!
  emailID: ID!
  emailAddress: EmailAddress! @connection(fields: ["emailID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type EmailAddress @model
  @key(fields: ["id"])
  @key(name: "emailsByEmail", fields: ["email", "id"])
  @key(name: "emailsByEntityID", fields: ["entityID", "id"])
  @key(name: "primaryEmailsByEntityID", fields: ["entityID", "id", "primary"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  entityID: ID # ownerID # set once associated
  associatedEntityEmailAddressID: ID # set once associated
  type: EmailType!
  lock: LockType
  lockDate: Int
  email: String!
  emailVerified: Boolean!
  primary: PrimaryType!
  notifications: Boolean!
  visible: Boolean!
  secret: Boolean!
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"]) # no rights once created unless entityID matches owner
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type AssociatedEntityPhoneNumber @model
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  phoneID: ID!
  phoneNumber: PhoneNumber! @connection(fields: ["phoneID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type PhoneNumber @model
  @key(fields: ["id"])
  @key(name: "phonesByEntityID", fields: ["entityID", "id"])
  @key(name: "primaryPhonesByEntityID", fields: ["entityID", "id", "primary"])
  @key(name: "phonesByPhoneNumber", fields: ["phone", "id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  entityID: ID # set once associated
  associatedPhoneNumberID: ID # set once associated
  type: PhoneType!
  lock: LockType
  lockDate: Int
  phone: String!
  phoneVerified: Boolean!
  tollFree: Boolean!
  primary: PrimaryType!
  notifications: Boolean!
  visible: Boolean!
  secret: Boolean!
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

enum PhoneType {
  VOIP
  MOBILE
  LANDLINE
}

enum EntityType {
  UNKNOWN
  PERSON
  BUSINESS
  ORGANIZATION
  AGENCY
  API
}

enum EntitySecurity {
  USER
  ADMIN
}

enum LockType {
  CONFIRMATION
  ADMIN
  PASSWORD
  ABUSE
  BLACKLIST
}

type RandomAssociatedData @model
  @key(fields: ["id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  someData: String!
  createdAt: AWSTimestamp! # never update- just replace
}

# semipivot table- 1:M allows multiple entities per cognito entity(?)
type AssociatedCognitoEntity @model
  @aws_cognito_user_pools # https://aws.amazon.com/blogs/mobile/graphql-security-appsync-amplify/
  @auth(rules: [
    # Defaults to use the "owner" field.
    { allow: owner },

    # Authorize the update mutation and both queries. Use `queries: null` to disable auth for queries.
    { allow: owner, ownerField: "owner", operations: [update, read] }

    # Admin users can access any operation.
    { allow: groups, groups: ["Admin"] }
  ])
{
  id: ID!
  type: EntityType!
  cognitoID: String!
  owner: String # may be same as primaryEntityID?
  primaryEntityID: ID!
  primaryEntity: Entity! @connection(fields: ["primaryEntityID"])
  primaryRandomAssociatedDataID: ID!
  primaryRandomAssociatedData: RandomAssociatedData! @connection(fields: ["primaryRandomAssociatedDataID"])
  # all known entities associated with this cognito user
  entities: [Entity!]! @connection(fields: ["id"])
  # plus main props of base Cognito profile
  name: String
  email: String
  phone: String
  picture: String
  profile: String
  username: String
  gender: String
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

# originally what I was trying to use as auth class
type Entity @model
  @key(fields: ["id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  associatedCognitoEntityID: ID # set once associated
  RandomAssociatedDataID: ID! # ALL entities get a key
  RandomAssociatedData: RandomAssociatedData! @connection(fields: ["RandomAssociatedDataID"])
  type: EntityType!
  entitySecurity: EntitySecurity!
  lock: LockType
  lockDate: Int
  username: String
  usernameDate: Int
  password: String
  passwordDate: Int
  name: String
  nameDate: Int
  gender: String
  genderDate: Int
  picture: String
  pictureVisible: Boolean!
  pictureDate: Int
  link: String
  linkConfirmed: Boolean!
  linkVisible: Boolean!
  linkDate: Int
  # proven claimed for this identity - AUTHORITY
  # TODO: NOTE: multiple emails/phones/entities/cognitos with conflicting attributes (phone/email) will/should be in conflict/prevented
  # users ideally shouldn't have multiple entities per cognito
  # and even more ideally shouldn't any phones or emails on non primary entities
  # hopefully uniqueness of phone number and email will prevent one cognito having
  # two+ entities with the same phones or emails. They may be forced to move a phone/email
  # over if they log in with a cognito that matches a phone from one entity and an email
  # from another.
  # Similarly, if a user logs in with another provider, creating a new account with the same
  # email as an existing account, they should be blocked/the accounts should be merged.
  associatedPhones: [AssociatedEntityPhoneNumber!]! @connection(fields: ["id"]) # all phones, including one for primary
  associatedEmails: [AssociatedEntityEmailAddress!]! @connection(fields: ["id"]) # all emails, including one for primary
  # Votes ABOUT this entity - NO AUTHORITY
  CountedVotes: [CountedVote!]! @connection(name: "CountedVoteVoteID", fields: ["id"])
  # created BY this entity - AUTHORITY
  myVotes: [Vote!]! @connection(name: "CreatorEntityVotes", fields: ["id"])
  # groups this entity is participating in (created?)
  mygroups: [Group!]! @connection(fields: ["id"])
  # badged earned by this entity
  myEarnedBadges: [EarnedBadges!]! @connection(fields: ["id"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type Group @model
  @key(fields: ["id"])
  @key(name: "groupsByCreatorID", fields: ["creatorID", "id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  lock: LockType
  lockDate: Int
  memo: String
  memoDate: Int
  description: String
  descriptionDate: Int
  link: String
  linkVisible: Boolean!
  linkDate: Int
  hidden: Boolean!
  entities: [Entity!]! @connection(fields: ["id"]) # entities involved, NO authority
  Votes: [Vote!]! @connection(fields: ["id"]) # associated Votes
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"]) # AUTHORITY
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

# pivot table for Votes
type CountedVote @model
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  VoteID: ID!
  Vote: Vote! @connection(name: "CountedVoteVoteID", fields: ["VoteID"])
  entityID: ID!
  entity: Entity! @connection(name: "CountedVoteEntityID", fields: ["entityID"])
  someData: String!
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type Vote @model
  @key(fields: ["id"])
  @key(name: "VotesByCreatorID", fields: ["creatorID"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  CountedVoteID: ID # set once applied
  Vote: Int!
  VoteDate: Int!
  memo: String
  memoVisible: Boolean!
  link: String
  linkVisible: Boolean!
  linkDate: Int
  emailID: ID
  phoneID: ID
  groupID: ID
  visible: Boolean!
  hidden: Boolean!
  creatorID: ID!
  creator: Entity! @connection(name: "CreatorEntityVotes", fields: ["creatorID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type QueryRoot {
  badge(id: ID!): Badge @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getVoteByID(id: ID!): Vote @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getgroupByID(id: ID!): Group @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEntityByID(id: ID!): Entity @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEmailByID(id: ID!): EmailAddress @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEmailByEmail(email: String!): EmailAddress @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getPhoneByID(id: ID!): PhoneNumber @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getPhoneByPhoneNumber(phone: String!): PhoneNumber @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
}
JessicaMulein commented 4 years ago

I've made progress on the issue. I'll post an updated schema soon. Fixing all my @auth's missing @aws_api_key @aws_iam's etc.

JessicaMulein commented 4 years ago

With my now-fixed graphql (at least compilable), I get this- which I've encountered before and will now work to track down where that's coming from. Schema to follow.

DataStore does not support 1 to 1 connection with both sides of connection as optional field Error: DataStore does not support 1 to 1 connection with both sides of connection as optional field at Object.processConnections (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/utils/process-connections.ts:203:17) at /usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/visitors/appsync-visitor.ts:448:32 at Array.forEach () at /usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/visitors/appsync-visitor.ts:447:20 at Array.forEach () at AppSyncModelJavaVisitor.processConnectionDirective (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/visitors/appsync-visitor.ts:446:34) at AppSyncModelJavaVisitor.processDirectives (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/visitors/appsync-visitor.ts:241:10) at AppSyncModelJavaVisitor.generate (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/visitors/appsync-java-visitor.ts:22:10) at Object.exports.plugin (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/amplify-codegen-appsync-model-plugin/src/plugin.ts:49:20) at Object.executePlugin (/usr/local/lib/node_modules/@aws-amplify/cli/node_modules/@graphql-codegen/core/src/execute-plugin.ts:54:12)

JessicaMulein commented 4 years ago

I think this is again a representation of what is close to working now:

schema {
  query: QueryRoot
}

type Badge @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @key(fields: ["id"])
  @key(name: "badgesByCreatorID", fields: ["creatorID", "id"])
  @key(name: "badgesByTitle", fields: ["title", "id"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  title: String!
  description: String!
  enabled: Boolean!
  visible: Boolean!
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type EarnedBadges @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @key(fields: ["id"])
  @key(name: "earnedBadgesByBadgeID", fields: ["badgeID"])
  @key(name: "earnedBadgesByEntityID", fields: ["entityID"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  badgeID: ID!
  badge: Badge! @connection(fields: ["badgeID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

enum EmailType {
  WORK
  HOME
  PERSONAL
  BUSINESS
}

enum PrimaryType {
  PRIMARY
  OTHER
}

type AssociatedEntityEmailAddress @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  emailID: ID!
  emailAddress: EmailAddress! @connection(fields: ["emailID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type EmailAddress @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
  @key(fields: ["id"])
  @key(name: "emailsByEmail", fields: ["email", "id"])
  @key(name: "emailsByEntityID", fields: ["entityID", "id"])
  @key(name: "primaryEmailsByEntityID", fields: ["entityID", "id", "primary"])
{
  id: ID!
  entityID: ID # ownerID # set once associated
  associatedEntityEmailAddressID: ID # set once associated
  type: EmailType!
  lock: LockType
  lockDate: Int
  email: String!
  emailVerified: Boolean!
  primary: PrimaryType!
  notifications: Boolean!
  visible: Boolean!
  secret: Boolean!
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"]) # no rights once created unless entityID matches owner
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type AssociatedEntityPhoneNumber @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  phoneID: ID!
  phoneNumber: PhoneNumber! @connection(fields: ["phoneID"])
  entityID: ID!
  entity: Entity! @connection(fields: ["entityID"])
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type PhoneNumber @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
  @key(fields: ["id"])
  @key(name: "phonesByEntityID", fields: ["entityID", "id"])
  @key(name: "primaryPhonesByEntityID", fields: ["entityID", "id", "primary"])
  @key(name: "phonesByPhoneNumber", fields: ["phone", "id"])
{
  id: ID!
  entityID: ID # set once associated
  associatedPhoneNumberID: ID # set once associated
  type: PhoneType!
  lock: LockType
  lockDate: Int
  phone: String!
  phoneVerified: Boolean!
  tollFree: Boolean!
  primary: PrimaryType!
  notifications: Boolean!
  visible: Boolean!
  secret: Boolean!
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

enum PhoneType {
  VOIP
  MOBILE
  LANDLINE
}

enum EntityType {
  UNKNOWN
  PERSON
  BUSINESS
  ORGANIZATION
  AGENCY
  API
}

enum EntitySecurity {
  USER
  ADMIN
}

enum LockType {
  CONFIRMATION
  ADMIN
  PASSWORD
  ABUSE
  BLACKLIST
}

# semipivot table- 1:M allows multiple entities per cognito entity
type AssociatedCognitoEntity @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"]) # https://aws.amazon.com/blogs/mobile/graphql-security-appsync-amplify/
  @auth(rules: [
    # Defaults to use the "owner" field.
    { allow: owner },

    # Authorize the update mutation and both queries. Use `queries: null` to disable auth for queries.
    { allow: owner, ownerField: "owner", operations: [create, update, read] }

    # Admin users can access any operation.
    { allow: groups, groups: ["Admin"] }
  ])
{
  id: ID!
  type: EntityType!
  cognitoID: String!
  owner: String # may be same as primaryEntityID? cognitoID?
  primaryEntityID: ID!
  primaryEntity: Entity! @connection(fields: ["primaryEntityID"])
  # all known entities associated with this cognito user
  entities: [Entity!]! @connection(fields: ["id"])
  # plus main props of base Cognito profile
  name: String
  email: String
  phone: String
  picture: String
  profile: String
  username: String
  gender: String
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type Entity @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
  @key(fields: ["id"])
{
  id: ID!
  associatedCognitoEntityID: ID # set once associated
  type: EntityType!
  entitySecurity: EntitySecurity!
  lock: LockType
  lockDate: Int
  username: String
  usernameDate: Int
  password: String
  passwordDate: Int
  name: String
  nameDate: Int
  currentVote: Float
  currentVoteID: ID
  gender: String
  genderDate: Int
  picture: String
  pictureVisible: Boolean!
  pictureDate: Int
  link: String
  linkConfirmed: Boolean!
  linkVisible: Boolean!
  linkDate: Int
  # proven claimed for this identity - AUTHORITY
  # TODO: NOTE: multiple emails/phones/entities/cognitos with conflicting attributes (phone/email) will/should be in conflict/prevented
  # users ideally shouldn't have multiple entities per cognito
  # and even more ideally shouldn't any phones or emails on non primary entities
  # hopefully uniqueness of phone number and email will prevent one cognito having
  # two+ entities with the same phones or emails. They may be forced to move a phone/email
  # over if they log in with a cognito that matches a phone from one entity and an email
  # from another.
  # Similarly, if a user logs in with another provider, creating a new account with the same
  # email as an existing account, they should be blocked/the accounts should be merged.
  associatedPhones: [AssociatedEntityPhoneNumber!]! @connection(fields: ["id"]) # all phones, including one for primary
  associatedEmails: [AssociatedEntityEmailAddress!]! @connection(fields: ["id"]) # all emails, including one for primary
  # votes ABOUT this entity - NO AUTHORITY
  appliedVotes: [CountedVote!]! @connection(name: "appliedVoteVoteID", fields: ["id"])
  # created BY this entity - AUTHORITY
  myVotes: [Vote!]! @connection(name: "CreatorEntityVotes", fields: ["id"])
  # randomDatas this entity is participating in (created?)
  myRandomDatas: [RandomAssociatedData!]! @connection(fields: ["id"])
  # badged earned by this entity
  myEarnedBadges: [EarnedBadges!]! @connection(fields: ["id"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type RandomAssociatedData @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
  @key(fields: ["id"])
  @key(name: "randomDatasByCreatorID", fields: ["creatorID", "id"])
{
  id: ID!
  lock: LockType
  lockDate: Int
  memo: String
  memoDate: Int
  description: String
  descriptionDate: Int
  link: String
  linkVisible: Boolean!
  linkDate: Int
  hidden: Boolean!
  entities: [Entity!]! @connection(fields: ["id"]) # entities involved, NO authority
  votes: [Vote!]! @connection(fields: ["id"]) # associated votes
  creatorID: ID!
  creator: Entity! @connection(fields: ["creatorID"]) # AUTHORITY
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

# pivot table for votes
type CountedVote @model
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @aws_api_key @aws_iam
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
{
  id: ID!
  voteID: ID!
  vote: Vote! @connection(name: "appliedVoteVoteID", fields: ["voteID"])
  entityID: ID!
  entity: Entity! @connection(name: "appliedVoteEntityID", fields: ["entityID"])
  # timestamps
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type Vote @model
  @aws_api_key @aws_iam
  @aws_cognito_user_pools(cognito_groups: ["Users", "Admin"])
  @auth(rules: [
    { allow: public, operations: [read] }
  ])
  @key(fields: ["id"])
  @key(name: "votesByCreatorID", fields: ["creatorID"])
{
  id: ID!
  appliedVoteID: ID # set once applied
  vote: Int!
  voteDate: Int!
  memo: String
  memoVisible: Boolean!
  link: String
  linkVisible: Boolean!
  linkDate: Int
  emailID: ID
  phoneID: ID
  randomDataID: ID
  visible: Boolean!
  hidden: Boolean!
  creatorID: ID!
  creator: Entity! @connection(name: "CreatorEntityVotes", fields: ["creatorID"])
  createdAt: AWSTimestamp!
  updatedAt: AWSTimestamp!
}

type QueryRoot {
  badge(id: ID!): Badge @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getVoteByID(id: ID!): Vote @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getRandomDataByID(id: ID!): RandomData @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEntityByID(id: ID!): Entity @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEmailByID(id: ID!): EmailAddress @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getEmailByEmail(email: String!): EmailAddress @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getPhoneByID(id: ID!): PhoneNumber @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
  getPhoneByPhoneNumber(phone: String!): PhoneNumber @aws_api_key @aws_auth(cognito_groups: ["Users", "Admin"])
}
JessicaMulein commented 4 years ago

This one's interesting: if you give fields: "blah" instead of fields: []

jessica.mulein@Jessicas-MBP  /Volumes/Code/XXXXX   AmplifySchemaWIP ●✚  amplify api gql-compile TypeError: args.fields.forEach is not a function

JessicaMulein commented 4 years ago

Found a couple mistakes in a schema I posted, and that I also revealed part of a key detail, so I deleted that comment...

In any case- when I fix those, it re-breaks it back to where it compiles but gets a "DataStore does not support 1 to 1 connection with both sides of connection as optional field" error. I assume re-walking back through my UML diagram and the schema and paying attention to the points I noted above I'll get it back to square again.

It's just a really laborious process that you really need to keep your stuff straight for and have a really strong idea about what you're looking for and why-- which is /really/ hard coming out of the gates from scratch to understand. It spits out odd errors (as above) in the case of certain mistakes... and those are really hard to know what might be causing it until you've fixed a particular pattern a few times.

It comes down to a lot of repetition, understanding the amplify-cli source, and how it's trying to break it down and getting a picture in your head of all these anchor points between these models. I think there's got to be another notation to this that makes it easier to comprehend and track which side is creating the source and which parts(s) is, are, or should be sinking it...

It's getting late and I'm about out of eyeball cycles on this for now.

Just to beat the dead horse too- Better (or a better understanding of how to set up) IDE awareness would /greatly/ help too. Being able to cmd-click back and forth to source types and usages would be handy, or getting pop-up help on the @directives.

I'll re-post a fixed schema tomorrow that doesn't have any references to proprietary information in it- and hopefully after I nail down this last glitch.

PS It looks like there's a nice point at which you could simply add a CLI argument that does a --print-map and if you manage to get to the point the schema compiles but you get the 1-1 error, just before that it has at least managed to fill in the this.modelMap and you could more or less object dump it if --print-map is specified- so if it bombs you at least grab that before it proceeds on to its death.