Role privilege on related collection

Hello, I’m trying to set up a role with privileges that are based on a related collection. For example I have a setup similar to the below data

{
  "ref": Ref(Collection("users"), "269756182088909300"),
  "ts": 1593518393440000,
  "data": {
    "email": "user@gmail.com"
  }
}

{
  "ref": Ref(Collection("shops"), "269756182088909400"),
  "ts": 1593518393440000,
  "data": {
    "owner": Ref(Collection("users"), "269756182088909300")
  }
}

So I have a users collection for all the users and a shops collection with the shops where each shop is owned by a user. I also have a products collection where a product could look like something below

{
  "ref": Ref(Collection("products"), "269756182088909500"),
  "ts": 1593518393440000,
  "data": {
    "shop": Ref(Collection("shops"), "269756182088909400")
  }
}

So each product is part of a shop which in turn is owned by a user. What I would like to do is to create a role for users that allows them to create a product inside the shop that they own but I’m struggling to find the correct syntax.

For example I have a role with the following privileges definition taken from the example but I would like to extend it so that it check if the current user is owning the shop instead of the product.

{
  resource: Collection("products"),
  actions: {
    ...,
    create: Query(
      Lambda(
        "values",
        Equals(Identity(), Select(["data", "owner"], Var("values")))
      )
    ),
    ...
  }
},

I tried doing a select on the user based on the owner but I got some set not found error so I’m guessing it should be done in some other way.

Any suggestions on how I could go about to accomplish this?

Based on your Document structure, there is no product.owner field, which is what you are trying to select in your Role.

You should be able to Get the shop from the product, and then Select owner from there.

{
  resource: Collection("products"),
  actions: {
    /*...*/
    create: Query(
      Lambda(
        "values",
        Let(
          {
            shopRef: Select(["data", "shop"], Var("values")),
            shopDoc: Get(Var("shopRef")),
            ownerRef: Select(["data", "owner"], Var("shopDoc"))
          },
          Equals(Identity(), Var("ownerRef"))
        )
      )
    ),
    /*...*/
  }
}
1 Like

Thx @ptpaterson. That is working as it should. Is there any difference to abstract the variables or could everything be in one line as well?

1 Like

You can make it all one line. I just like using Let when trying to be more clear about what’s happening.

Equals(Identity(), Select(["data", "owner"], Get(Select(["data", "shop"], Var("values")))))

should work just fine! Just not as easy on the eyes :nerd_face:

To be more general to other cases

Mostly a matter of convenience from what I can tell.

cached read ops

Read ops are cached. That means, for example, multiple calls to Get a specific Ref only incur a single read op. One might be concerned that you NEED to create a Let binding and reuse that as a variable, but if it more convenient for you to re-Get something in multiple places that’s fine.

For example, the following are effectively the same:

q.Let(
  {
    doc: Get(Var('ref'))
  },
  {
     field1: Select(['data', 'field1'], Var('doc')),
     field2: Select(['data', 'field2'], Var('doc')),
  }
)

// AND

{
   field1: Select(['data', 'field1'], Get(Var('ref'))),
   field2: Select(['data', 'field2'], Get(Var('ref'))),
}

reusing results from writes

Perhaps the only reason I have found that you NEED to use Let is carrying past operations forward.

Also, you can not directly create a Ref for a thing you just created. I want to say this is because all queries are transactions, so it’s like they wouldn’t be posted or something. In any case…

bad:

Do(
  CreateCollection({ name: 'things' }),
  // next line will error with reference not found
  CreateIndex({ source: Collection('things'), /*...*/ }) 
)

good:

Let(
  {
    newCol: CreateCollection({ name: 'things' }),
    newColRef: Select('ref', Var('newCol')),
    newIndex: CreateIndex({ source: Var('newColRef'), /*...*/ }),
    // added benefit of reusing lots of places
    newIndex: CreateIndex({ source: Var('newColRef'), /*...*/ }),
    newIndex: CreateIndex({ source: Var('newColRef'), /*...*/ }),
  },
  /*...*/
)

Or if you’re a crazy person (jk :stuck_out_tongue:)

CreateIndex({ source: Select('ref', CreateCollection({ name: 'things' }), /*...*/ })

Scoped Variables

This is my favorite.

Lambda('ref', Let(
  {
    instance: Get(Var('ref'))
  },
  Let(
    {
      ref: Select(['data', 'A'], Var('instance')),
      instance: Get(Var('ref'))
    },
    Let(
      {
        ref: Select(['data', 'C'], Var('instance')),
        instance: Get(Var('ref'))
      },
    )
  )
))

Turtles the whole way down. Makes it easy to create recursive helper functions!

1 Like