Using another table field as owner field for DataStore Authentication - aws-appsync

Which Category is your question related to?
DataStore (GraphQL API)
Amplify CLI Version
7.6.23
What AWS Services are you utilizing?
DataStore (GraphQL API)
Provide additional details e.g. code snippets. Be sure to remove any sensitive data.
I am trying to build a service where people can buy subscriptions to certain "Persons" and consume their information. I want to restrict people so that they can only access the data of a certain medium when they are subscribed to it.
Here is the basic structure:
type Post #model {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
}
type Person #model {
id: ID!
name: String!
posts: [Post] #hasMany(indexName: "byPerson", fields: ["id"])
}
type Subscription #model {
id: ID!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
userSub: String! // or whatever data we need to reference the user
}
So we have Subscriptions to Persons and Persons can have multiple posts. It is not necessary to fetch a Person if you want to fetch the Posts that a user should be able to see.
What should be possible:
Users should only be able to fetch the posts of the persons that they are subscribed to. There are two ways that I can think of doing but they all require me to change/update data. Since all the data is present, I am not a fan of such solutions.
Solution #1:
Add a group to each user, attach it to the post and add the user to the group as soon as he subscribed
type Post #model #auth(rules: [{ allow: groups, groupsField: "groups" }]) {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
groups: String!
}
Not a fan, it requires me to create a group each time a Person is created and I basically have duplicated information here with each post.
Solution #2:
Use an owner field and attach the user as soon as he subscribes
type Post #model #auth(rules: [{ allow: owner, ownerField: "subscribers" }]) {
id: ID!
text: String!
personID: ID! #index(name: "byPerson")
person: Person! #belongsTo(fields: ["personID"])
subscribers: [String]
}
Not a fan as well, I need to edit all the postings as soon as a user subscribes/cancels his subscriptions. The margin of error and amount of calculations here could be huge
I have thought about using a custom resolver (no idea if that works, I don't fully understand it yet) or a custom lambda auth check. The custom lambda auth check causes some issues in the frontend with DataStore. Apparently I need to manually refresh the token for the API or something like that.
What do I want to do?
I would love to use the subscription userSub field as an owner field for the posts. Is that possible (with DataSync) in any way?

Related

How can I get nested refs in OpenAPI?

I'm generating an API in Stoplight and I'm pretty new at it so I'll describe what I'd like to do in words first.
This is a contrived example but here goes. I have a User object with properties: ID, Name, Password, and an array of EmailAddress objects. The EmailAddress object has the following properties: ID, Address, IsPrimary.
When I create my schema for both User and EmailAddress, I'm leaving the ID fields off. The reason I'm doing this is because in the POST for /users, the ID won't be included for either object type in the request body. So the request for the POST looks like so (responses omitted for now):
post:
summary: ''
operationId: post-users
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/User'
So far so good. But in the response, I'd like to return the full User object with an ID attached. Easy enough, I says (requestBody omitted):
post:
summary: ''
operationId: post-users
responses:
'201':
description: Created
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/User'
- $ref: '#/components/schemas/Id'
(where the Id schema contains just a string property named id)
Where I'm confused is for the EmailAddress objects. I'd like to return those with IDs attached to them but I don't know how to "inject" the IDs in there. I.e. I want to send a User without an ID and with an array of EmailAddress objects also without IDs. But I want to return a User with an ID and with an array of EmailAddress objects with IDs.
Do I need to create a separate UserRequest and UserResponse schema in this case, one with EmailAddress objects with IDs and one without?
Larger question: is returning the entire object in a POST a common thing? I've seen references that say to just return the ID of the new object and others to say return the entire thing to save an extra call to the server to GET it again after the POST completes.

Implementing 3 way relationship in Prisma

I also asked this question on Prisma forum.
** EDIT **: The forum has since been locked as read-only just 1 day after my question. It's sure getting scary because there is no official announcement of whether they'll stop developing Prisma 1 after implementing promised features or not. Surely they could have said something. And TBH, the unstable Prisma site does add to my shaking confidence in Prisma despite the number of stars in their repo.
I'm new to Prisma. So I have a 3-way relationship between User, Event and Role I would like to define. For each association of a User and an Event, there exists a Role for that association. If I were to design a database table for another ORM, I would have created a event_user table with user_id, event_id and role_id as columns to relate them.
Practically, a row of these 3 columns must be unique. Obviously, it would be good if Prisma can do the safeguarding of these constraints, but the obvious solution I see might not even come to Prisma 1.
My current design consists of the following:
type User {
// ...
eventUsers: [EventUser!]!
}
type Event {
// ...
eventUsers: [EventUser!]!
}
type EventUser {
role: Role!
event: Event!
user: User!
}
This design will make render all xxWhereUniquexx unusable, which is definitely a hassle to maintain relationships. upserts will certainly be unusable for maintaining the relationships.
How would one create a relationship like this in Prisma?
For some context, each user would have a list of global roles as well, so there would already be an association between User and Role. The question concerns the "local roles" for each event.
If each user already have a value for their respective roles, there would be no need for a third table (unless there is more information you'd like to be stored in your modal layer in which case the role type should be in the Role table).
Relationships are set with the #relation directive. You can apply it to either one of two tables in a relation but for clarity I'm apply them to the Event table. The example assumes users can be part of several events.
Tables:
enum ROLE_TYPE {
TYPE_ONE
TYPE_TWO
}
User {
user_id: ID! #id
events: [Event!]!
}
Event {
event_id: ID! #id
users: [User!]! #relation(link: INLINE)
}
Role {
role_id: ID! #id
type: ROLE_TYPE
event: Event! #relation(link: INLINE)
user: User! #relation(link: INLINE)
}
You can read more about relations on the Prisma site

The Mongo connector currently does not support Cascading Deletes

I'm working with Prisma and I want to support CASCADE delete but although I have done everything mentioned in the docs I'm still not getting it to work
this is the error I'm getting when I try to Prisma deploy
Errors:
Restaurant
✖ The Mongo connector currently does not support Cascading Deletes, but the field `foods` defines cascade behavior. Please remove the onDelete argument.}
Here is the code
type Restaurant {
id: ID! #id
name: String!
foods: [Food!]! #relation(onDelete: CASCADE, name: "FoodToRestaurant", link: INLINE)
}
type Food {
id: ID! #id
name: String!
desc: String
price: Float!
category: Category!
restaurant: Restaurant! #relation(name: "FoodToRestaurant", onDelete: SET_NULL)
}
I expect when the restaurant gets deleted all its foods should also be deleted
I have CASCADE deleted with Prisma PostgreSQL but now I want to use MongoDB for this app
As it's not possible, you should manage it manually.
Means you should delete all of related entities recursively.
For example if it's your db schema:
Question -> Comment -> Rate
If you want to delete a question, you should delete all of comments related to that question, and if you want to delete a comment, you should delete all of rates assigned to that comment. So you need some recursive functions for deleting these entities.
function deleteQuestion(questionId) {
for each comment related to questionID
deleteComment(commentId)
delete(questionId)
}
function deleteComment(commentId) {
for each rate related to commentId
deleteRate(rateId)
delete(commentId)
}
function deleteRate(rateId) {
delete(rateId)
}
currently mongodb prisma not support cascading delete. if you have it in your schema please remove it.

How to query custom defined join table

I have three models user, tab, subscription. User can subscribe to many tabs and tab can have many subscribers.
user --* subscription *-- tab
Diagram from navicat
sequelize.define('subscription', {})
Tab.belongsToMany(User, { through: Subscription })
User.belongsToMany(Tab, { through: Subscription })
How do I get all subscribers for specific tab or all tabs which specific user subscribes to?
I could do that in two queries but it have to be possible in one.
I have tried every possible combination of where, include, through which I would rather not share here. Every time I try to get access to subscription from any side I get subscription (subscriptions) is not associated to user.
I am trying to get something like this to work.
await Tab.findAll({
where: { name: ctx.params.tabname },
include: [{
model: Subscription,
through: {
where: { userUsername: ctx.params.username }
}
}]
})
The issue with belongsToMany is that is creates link directly to the destination entity. So to get all users for a specific tab you would have to do
Tab.findAll({
include: [{
model: User
}]
});
That works when you don't really want to get any data from the subscription entity.
In a case you want to select any data from the subscription entity you need to slightly modify your relations.
Tab.hasMany(Subscription);
Subscription.belongsTo(Tab);
User.hasMany(Subscription);
Subscription.belongsTo(User);
This will create one to many relation from tab to subscription and add also binding backwards from subscription to tab. The same thing for user.
Then you can query for all subscriptions (with user info) of a specific tab or for all subscriptions (with tab info) for a specific user like following:
Tab.findAll({
include: [{
model: Subscription,
include: [{
model: User
}]
}]
});
User.findAll({
include: [{
model: Subscription,
include: [{
model: Tab
}]
}]
});
I hope that helps!

How to manage drafts and multiple versions in Mongo?

This is a follow-up to my previous question.
Suppose there is a product catalog stored as a collection in Mongo. User Alice is a catalog manager and may update, remove and add products to the catalog. User Bob is a customer and may view the catalog.
Currently when Alice changes the catalog Bob sees the changes immediately. Now we want the changes to be visible only if Alice explicitly publish them. For example:
There is a catalog which consists of Product A, Product B, and Product C. Both Alice and Bob see the same products.
Alice changed the catalog. She modified Product A, removed Product C, and added Product D but did not publish the changes.
Now Alice sees Product A' (modified), Product B, and Product D but Bob still sees the previous version: Product A, Product B, and Product C.
Alice published the catalog. Now both Alice and Bob see the same products: Product A' (modified), Product B, and Product D
My questions are:
how to implement it with Mongo
how to manage versions/revisions of the catalog, so Alice will be able to undo/redo the changes she made in the catalog.
Ahh temporal data, the bane of database developers everywhere.
Fortunately this is arguably easier in mongodb than other relational dbs.
If you can make the assumption that you'll have at most ONE unpublished version this problem is much simpler than if you can have different users editing unpublished versions.
Assuming you've got some standard things in your schema:
{
_id: ObjectId
name: String,
CreatedDate: Date,
Price: Number
}
You need to add a sub-document with a duplicate of any field editable by the user. It also will contain a flag for deletion.
{
_id: ObjectId
name: String,
createdDate: Date,
price: Number,
revised: {
name: String,
createdDate: Date,
price: Number,
deleted: Boolean,
}
}
When a user goes to edit the product, you'll copy over the existing props into the 'revised' object. All edits go to that object. When you publish, you copy those items back to the base layer, and delete the 'revised' property.
If you have multiple users editing the document, and they can't see each other's edits you could make your revised document a bit more complicated
{ revised: { U1234: { name : ... }, U2345 : { name: ... } } }
Where each user has a separate copy. Of course when one user publishes it could delete the entity entirely. I would of course recommend adding a 'deleted' flag to the root item instead of actually deleting it from the db, unless these objects are HUGE. (Index the deleted flag of course.)