amplify-category-api icon indicating copy to clipboard operation
amplify-category-api copied to clipboard

How do we point two different types to the same DynamoDB table?

Open skryshi opened this issue 3 years ago • 5 comments

For new Amplify (>=7.5.0), how do we point two different types to the same DynamoDB table? Couldn't find this anywhere in the docs.

For example, how can I have both User and UserPublic read/mutate the same DynamoDB table? I tried mucking around with CustomResources.json, but couldn't get it to work:

type User @model {
   name: String
   ssn: String
}

type UserPublic @model {
   name: String
}

Thank you!

skryshi avatar Nov 28 '21 14:11 skryshi

Can you explain your use case a bit more. It seems like this problem could be solved by using auth to control access to a single table.

cjihrig avatar Nov 28 '21 21:11 cjihrig

@cjihrig There are plenty of use cases that cannot be solved with @auth. Whenever my signed-in users need more than 2 groups of access, I am stuck.

Example 1:

Take a User model in a basic social network. Consider User A:

  1. User A needs full access to User A model: @auth(rules: [{ provider: userPools, allow: owner }])
  2. Friends of User A need partial access to User A model
  3. Non-friends of User A need light access to User A model: @auth(rules: [{ provider: userPools, allow: private, operations: [read] }])
  4. Guest users have no access to User A model: @auth(rules: [{ allow: public, operations: [] }])

There is no way for me to setup permissions for (2) using @auth. But with a secondary type UserForFriends pointing at Users table, it's trivial:

type User @model @auth(rules: [{ provider: userPools, allow: owner }]) {
   name: String
   favoriteMovie: String
   ssn: String
}

type UserForFriends @model @auth(rules: [{ provider: userPools, allow: private, operations: [read] }]) {
   name: String
   favoriteMovie: String
}

type UserPublic @model @auth(rules: [{ provider: userPools, allow: private, operations: [read] }]) {
   name: String
}

Example 2:

Take a User model in a basic dating app (e.g. Tinder). Consider User A:

  1. User A needs full access to User A model: @auth(rules: [{ provider: userPools, allow: owner }])
  2. Matched users need partial access to User A model
  3. Potential matches need light access to User A model: @auth(rules: [{ provider: userPools, allow: private, operations: [read] }])
  4. Guest users have no access to User A model: @auth(rules: [{ allow: public, operations: [] }])

Same as above, there is no way for me to setup permissions for (2) using @auth. But with a secondary type UserForMatched pointing at Users table, it's trivial.


I just tried another approach, using amplify override api with override.ts, but it didn't work either:

import { AmplifyApiGraphQlResourceStackTemplate } from '@aws-amplify/cli-extensibility-helper';

export function override(resources: AmplifyApiGraphQlResourceStackTemplate) {
  resources.models["UserPublic"].modelDDBTable = resources.models["User"].modelDDBTable
  resources.models["UserPublic"].modelIamRole = resources.models["User"].modelIamRole
  resources.models["UserPublic"].dynamoDBAccess = resources.models["User"].dynamoDBAccess
  resources.models["UserPublic"].modelDatasource = resources.models["User"].modelDatasource
}

How do I do this? Please, help.

skryshi avatar Nov 29 '21 03:11 skryshi

There are plenty of use cases that cannot be solved with @auth. Whenever my signed-in users need more than 2 groups of access

Sorry, I was referring to field level auth. Have you looked into that at all?

cjihrig avatar Nov 29 '21 14:11 cjihrig

@cjihrig Yes, I have looked at field level auth. Whether I use model- or field-level auth, it doesn't help when I have 3+ groups of signed-in users.

Taking example (1) above, I have the owner with allow: owner permissions, a signed-in user with allow: private permissions, but I don't have allow: friends permissions for owner's friends.

Right now, to create allow:friends permission, I have to

  1. Create a custom authorizer that checks whether two users are friends.
  2. Create a custom allow/deny fields resolver.

If I could point two types at the same AppSync datasource, then I could skip step (2) while making the interface for allow: friends permission explicit in schema.graphql.

skryshi avatar Nov 30 '21 06:11 skryshi

I'm landing here because I'm trying to implement a dual approval strategy where only one user can initiate the approval process.

My idea of schema.graphql is:

type sendApproval @model(subscriptions: null, timestamps: {createdAt: "createdAt", updatedAt: "sendUpdatedAt" }) @auth(rules: [{ allow: owner, ownerField: "sendOwner"}]) {
    id: ID!
    sendOwner: String!
    rcptOwner: String!
    contractId: String! 
    sendStatus: sendStatusType!
    createdAt: AWSDateTime
    sendUpdatedAt: AWSDateTime
}

type rcptApproval @model(subscriptions: null, timestamps: {createdAt: "rcptCreatedAt", updatedAt: "rcptUpdatedAt" }) @auth(rules: [{ allow: owner, operations: [read], ownerField: "rcptOwner"}]) {
    id: ID! @auth(rules: [{ allow: owner, operations: [read]}])
    rcptOwner: String! @auth(rules: [{ allow: owner, operations: [read]}])
    rcptStatus: String @auth(rules: [{ allow: owner, operations: [read,update]}])
    rcptUpdatedAt: AWSDateTime @auth(rules: [{ allow: owner, operations: [read,update]}])
    sendOwner: String @auth(rules: [{ allow: owner, operations: [read]}])
}

What I've tried is to tweak the table name with override.ts, but deploy fails with "table already exists" error because amplify is trying to create two tables with the same name.

export function override(resources: AmplifyApiGraphQlResourceStackTemplate, amplifyProjectInfo: AmplifyProjectInfo) {
    resources.models["sendApproval"].modelDDBTable.tableName = ApprovalTableName;
    resources.models["rcptApproval"].modelDDBTable.tableName = ApprovalTableName;
...
}

Having the possibility to use the same table would be great.

ggorge-etiqa avatar Apr 10 '24 15:04 ggorge-etiqa