How should I setup the schema for deletion subscription? - laravel

I tried
type Mutation {
deleteUser(id: ID!): User #delete #broadcast(subscription: "userDeleted")
}
type Subscription {
userDeleted(id: ID!): User
}
and I created a subcription where the methods authorize and filter return true.
But I get this error:
Illuminate\Database\Eloquent\ModelNotFoundException: No query results for model [App\User]
The deleteUser mutation works. Only the subscription does not work. I use Pusher for broadcast and the error appeared in the horizon dashboard.

If you really need a solution right now, just make a custom resolver where you firstly brodcast the event and then delete the user... (you can even make a custom directive that generalizes it).
Otherwise, you will have to dig a bit into the Lighthouse's internals to find a solution.

It might be too late for you by now, but could help future developers looking for a solution.
I found that you can trigger the subscription in the model's 'deleting' event using Laravel's model events: https://laravel.com/docs/7.x/eloquent#events This means that the model will exist in the database when the subscription gets it from the database, and shouldn't throw an error.
Ideally, the accepted solution would probably be the cleanest way to do it, but this works in the meantime.

Related

Amplify and AppSync not updating data on mutation from multiple sources

I have been attempting to interact with AppSync/GraphQL from:
Lambda - Create (works) Update (does not change data)
Angular - Create/Update subscription received, but object is null
Angular - Spoof update (does not change data)
AppSync Console - Spoof update (does not change data)
Post:
mutation MyMutation {
updateAsset(input: {
id: "b34d3aa3-fbc4-48b5-acba-xxxxxxxxxxx",
owner: "51b691a5-d088-4ac0-9f46-xxxxxxxxxxxx",
description: "AppSync"
}) {
id
owner
description
}
}
Response:
{
"data": {
"updateAsset": {
"id": "b34d3aa3-fbc4-48b5-acba-xxxxxxxxxx",
"owner": "51b691a5-d088-4ac0-9f46-xxxxxxxxxxx",
"description": "Edit Edit from AppSync"
}
}
The version in DynamoDB gets auto-incremented each time I send the query. But the description remains the same as originally set.
Auth Rules on Schema -
#auth(
rules: [
{ allow: public, provider: apiKey, operations: [create, update, read] },
{ allow: private, provider: userPools, operations: [read, create, update, delete] }
{ allow: groups, groups: ["admin"], operations: [read, create, update, delete] }
])
For now on the Frontend I'm cheating and just requesting the data after I received a null subscription event. But as I've stated I only seem to be able to set any of the data once and then I can't update it.
Any insight appreciated.
Update: I even decided to try a DeleteAsset statement and it won't delete but revs the version.
I guess maybe the next sane thing to do is to either stand up a new environment or attempt to stand this up in a fresh account.
Update: I have a working theory this has something to do with Conflict detection / rejection. When I try to delete via AppSync direct I get a rejection. From Angular I just get the record back with no delete.
After adding additional Auth on the API, I remember it asked about conflict resolution and I chose "AutoMerge". Doc on this at https://docs.aws.amazon.com/appsync/latest/devguide/conflict-detection-and-sync.html
After further review I'll note what happened in the hopes it helps someone else.
Created amplify add api
This walked me thru a wizard. I used the existing Cognito UserPool since I had not foreseen I would need to call this API from a S3 Trigger (Lambda Function) later.
Now needing to grant apiKey or preferably IAM access from the Lambda to AppSync/GraphQL API I performed amplify update api and added the additional Auth setting.
This asked me how I wanted to solve conflict, since more than one source can edit the data. Because I just hit "agree" on Terms and Conditions and rarely read the manual; I selected 'AutoMerge' .. sounds nice right?
So now if you read the fine print, edits made to a table will be rejected as we now have this _version (Int) that would need to get passed so AutoMerge can decide if it wants to take your change.
It also creates an extra DataStore Table in DynamoDB tracking versions. So in order to properly deal with this strategy you'd need to extend your schema to include _version not just id or whatever primary key you opted to use.
Also note: if you delete it sets _delete Bool to true. This actually still is returned to the UI so now your initial query needs to filter off (or not) deleted records.
Determined I also didn't need this. I don't want to use a Datastore (least not now) so: I found the offender in transform.conf.json within the API. After executing amplify update api, GraphQL, I chose 'Disable Datastore for entire API` and it got rid of the ConflictHandler an ConflictDetection.
This was also agitating my Angular 11 subscription to Create/Update as the added values this created broke the expected model. Not to mention the even back due to nothing changing was null.
Great information here, Mark. Thanks for the write up and updates.
I was playing around with this and with the Auto Merge conflict resolution strategy I was able to post an update using a GraphQL mutation by sending the current _version member along.
This function:
await API.graphql(
graphqlOperation(updateAsset, {
input: {
id: assetToUpdate.id,
name: "Updated name",
_version: assetToUpdate._version
}
}
));
Properly updates, contacts AppSync, and propagates the changes to DynamoDB/DataStore. Using the current version tells AppSync that we are up-to-date and able to edit the content. Then AppSync manages/increments the _version/_createdAt/etc.
Adding _version to my mutation worked very well.
API.graphql({
query: yourQuery,
variables: {
input: {
id: 'your-id',
...
_version: version,
},
},
});

Can Queries be used for data writing?

For my GraphQL app I'd like to save logs of certain resolved fields. Because the users can view these logs themselves, should that be considered apart of a mutation instead of a query?
Since it's not the application's focus I'd assume that using a mutation is overkill, but I'm not sure if there's some sort of side effects I'm going to run into by modeling it in such a way.
The other questions I've read didn't really answer this question, so sorry if this seems like a duplicate.
Conceptually Graphql Queries & Mutations do the same thing but however differ in the way the resolvers are executed.
For the following Queries:
{
user {
name
}
posts {
title
}
}
The GraphQL implementation has the freedom to execute the field entries in whatever order it deems optimal. see here.
For the following Mutations:
{
createUser(name: $String) {
id
}
addPost(title: $String) {
id
}
}
The GraphQL implementation would execute each Mutation sequentially. see here
Par from this, the Mutation keyword is just a bit of syntax to say "hey this is gonna edit or create something". I think here, in your case, its a better decision to perform a Query & store the event in your Audit log. Exposing the fact that the Query stores an audit log is an implementation-specific detail & clients shouldn't know about it.

How to return partial types with Apollo

In my app I have alerts. Partial schema:
type Alert {
id: ID!
users: [User]
}
type User {
id
username
... many more calculated fields
}
Each alert can return a list of Users. This User type is computationally expensive to build and we really only need a couple of user fields to display on an alert. However, if we only partially build the user object, Apollo will cache these partial user objects and break other parts of the app that depend on "complete" User objects. Avoiding this would require fetchPolicy: "no-cache", and I'd like to retain that caching.
So I'm trying to avoid returning entire User objects just to support an Alert. I've run into this issue with other types as well and am struggling with the best way to architect for this. The only solution I've come up with is to create "partial types", which would be separate types with a subset of fields. For example:
type Alert {
id: ID!
users: [PartialUser]
}
type PartialUser {
id
username
name
}
This feels hacky and like it violates DRY principles.
Another way might be to manually update the cache after a query. This would avoid the caching of partial User objects, but still feels hacky. I also think that only mutations support the options.update method to manipulate the cache. So I'm a bit stuck and haven't found any guidance in the docs.
Are there any recommendations for approaching this problem?

graphql multiple mutations using prior mutation return results?

I understand that mutations are sequential, so it makes sense to me that if Mutation 1 creates an entity and returns an id, that Mutation 2 should have access to that id. However I don't see any examples online and can't seem to get it to work. I see that people say you need to handle this in the resolve function of your route but it seems like extra unnecessary code if I can get this in just the query.
For example I have the following where accounts belong to clients and hence need the clientId before being created. However this does not work...
mutation createClientAndAccount($account: AccountInput, $client: ClientInput){
createClient(client: $client){ clientId }
createAccount(account: $account, clientId: USE_CLIENT_ID_FROM_ABOVE) { ... }
}
I've also tried nesting mutations but didn't have much luck there either...
Is what i'm trying to do possible? Would the resolve function of createAccount have the return data from createClient?
This is not possible right now, though would be useful.
See this PR.
Maybe using a custom schema directive we could achieve that.
Schema stitching will be a better approach(though usually it is preferred in API Gateway for merging APIs from different services).
If this requirement is very rare in your application, simply creating a new API that can do both CreateClientAndAccount is enough.

Sentry & Laravel, getting users within a group. changing findAllUsersWithAccess to have pagination

I'm trying to find all users w/ a specific permissions list in Sentry with laravel. The problem is that Sentry::findAllUsersWithAccess() returns an array().
as stated in their github repository i pinpointed their code to be
public function findAllWithAccess($permissions)
{
return array_filter($this->findAll(), function($user) use ($permissions)
{
return $user->hasAccess($permissions);
});
}
right now, it gets all users and filter it out with users with permission list. the big problem would be when I as a developer would get the set of users, it'll show ALL users, i'm developing an app which may hold thousands of users and i only need to get users with sepcific permission lists.
With regards to that would love to use one with a ->paginate() capability.
Any thoughts how to get it without getting all the users.
Why dont you override the findAllWithAccess() method and write your own implementation, which uses mysql where instead of array_filter().
I dont know your project structure and the underlying db schema, so all i can give you atm is the link to the eloquent documentation Querying Relations (whereHas).
In case you dont know where to start: its always a good idea to look at the ServiceProvider (SentryServiceProvider, where the UserProvider, which holds the findAllWidthAccess() method, is registered). Override the registerUserProvider method and return your own implementation of the UserProvider (with the edited findAllWithAccess() method).
Hope that will point you in the right direction.
In Laravel you can do pagination manually on arrays:
$paginator = Paginator::make($items, $totalItems, $perPage);
Check the docs: http://laravel.com/docs/pagination

Resources