How to detach a listener for a Firestore collection made with AngularFire? - google-cloud-firestore

Here's my observer that listens to a collection.
interface Scientist {
name?: string | null,
born?: number | null,
accomplishment?: string | null
};
export class AppComponent {
scientist$: Observable<Scientist[]>;
constructor(public firestore: Firestore) {
this.scientist$ = collectionData(collection(firestore, 'scientists'));
}
}
Works great but how do I detach the listener?

unsubscribe is an interface for subscription, which is returned by this.scientist$.subscribe(...). It seems currently this.scientist$ is currently considered Observable instead.

Related

How to update composite type model in Prisma?

I am trying to implement updation in a composite-type model in Prisma.
Here is my data structure:
{
"name":"toy",
"data":{
"sports":{
"currentState":"false"
},
"business":{
"currentState":"false"
}
}
}
Here I my code for updating:
const updatedSource = await prisma.sources.update({
where: {
name: 'toy'
},
data: {
data: {
sports: {
currentState: "true"
}
}
},
})
Here is my schema file
type SourcesData {
business SourcesDataState
sports SourcesDataState
}
type SourcesDataState {
currentState StateData[]
}
type StateData {
title String
url String
}
model sources {
id String #id #default(auto()) #map("_id") #db.ObjectId
data SourcesData
name String #unique
}
When I execute the above logic I get error as:Unknown arg `sports` in data.data.sports for type SourcesDataUpdateEnvelopeInput. Did you mean `set`? Available args:
Please guide what I am missing while updating.
The TypeScript should be pretty helpful in telling you what arguments you can or cannot use when interacting with Prisma. I strongly recommend using a code editor that includes TypeScript typehinting/Intellisense so you can see errors and warnings about your TypeScript usage as you are developing with Prisma.
Where it says Available args in your error, that should tell you the arguments that prisma.sports.update actually expects. If I had to guess (this may not be accurate, but you HAVE to look at the TypeScript to know exactly what it's supposed to be), it should look something like this:
const updatedSource = await prisma.sources.update({
where: {
name: 'toy'
},
data: {
data: {
update: {
sports: {
update: {
currentState: {
set: ["true"]
}
}
}
}
}
},
})
I strongly recommend reading Prisma's documentation on updating related/nested records: https://www.prisma.io/docs/concepts/components/prisma-client/relation-queries#update-a-specific-related-record
let typeEncounter = await prisma.encounter.update({
where: {
id
},
data: {
[property]: {
update: {
[subProperty] : value,
},
},
},
}
)
I get a receive the error Unknown arg update in data..update
I have seen some people mention nesting updates but no official documentation and can't seem to get this straightened out. Anybody have any ideas? The property and subproperty are largely irrelevant here, just examples. The code works fine aside from updated a subfield of a type (mongoDB prisma). Without the update the entire type gets overwritten rather than the selected field.

How to display nested graphql object datatypes given mongoDB objectID?

I am writing an app with GraphQL and mongoose. I created a function to get all bookings inside MongoDB. A booking object contains a reference to another object called Service. When I store a booking, it will store the associated service object as an ObjectID in MongoDB. When I make a query in graphql to get all the bookings, graphql does not services and their field types since graphql only receives an objectID. How can I fix graphql?
You need to write a resolver for the serviceType in AppointmentBooking.
Query: {
async getAppointmentBookings() {
...
}
},
Mutation: {
...
},
AppointmentBooking: {
serviceType: async(parent, args, ctx, info) => {
// Here you will get the objectId from the parent that need to query services
// This will call for every object inside the bookings
// Assuming you are storing the objectID for the services in the key servicetype
const serviceId = parent.serviceType;
try {
const serviceDetails = await Sevice.findByID(serviceID) ;
return serviceDetails;
} catch (err) {
throw new Error(err);
}
}
}

AWS Amplify AppSync Subscription not working correctly

I wrote a small application that subscribes to DB changes using AWS Amplify CLI / AppSync. All amplify api calls work perfectly (mutations, queries) but unfortunately the observer does not receive events. I can see that the MQTT socket receives periodically binaries but I can't obtain changed objects.
I configured Amplify for amplify use. I can see in the debugger that the AppSyncProvider has been initisalised. Also tried API and PubSub but makes no difference.
const awsmobile = {
"aws_appsync_graphqlEndpoint": "https://[...].appsync-api.[...]/graphql",
"aws_appsync_region": "[...]",
"aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",
};
Amplify.configure(awsmobile);
ngOnInit()
{
try {
this.apiService.OnUpdateA.subscribe(
{
next: (x) => {[...]},
error: (e) => {[...]},
complete: () => {[...]}
});
}
catch (error) {[...] }
}
***Schema***
type A
#model
#auth(rules: [
{allow: owner},
{allow: groups, groups: ["A"], operations: [create, update, read]},
{allow: groups, groups: ["B"], operations: [read]},
])
{
id: ID!
entry: AnotherType! #connection(name: "AnotherConnection")
[...]
}
OnUpdateAListener: Observable<
OnUpdateASubscription
> = API.graphql(
graphqlOperation(
`subscription OnUpdateA($owner: String) {
onUpdateA(owner: $owner) {
__typename
id
owner
[...]
}
}`
)
) as Observable<OnUpdateASubscription>;
Anyone for any ideas?
**Logs:**
{mqttConnections: Array(1), newSubscriptions: {…}, provider: Symbol(INTERNAL_AWS_APPSYNC_PUBSUB_PROVIDER)}
mqttConnections: Array(1)
0: {url: "wss://[...]-ats.iot.[...].amazonaws…[...]%3D%3D", topics: Array(2), client: "[...]"}
length: 1
__proto__: Array(0)
newSubscriptions:
onUpdate:
expireTime: 1573313050000
topic: "[....]/22tmaezjv5555h4o7yreu24f7u/onUpdate/1cd033bad555ba55555a20690d3e04e901145776d3b8d8ac95a0aea447b273c3"
__proto__: Object
__proto__: Object
provider: Symbol(INTERNAL_AWS_APPSYNC_PUBSUB_PROVIDER)
__proto__: Object
However, not sure whether it is suspicious that the subscription Object has no queue?
Subscription {_observer: {…}, _queue: undefined, _state: "ready", _cleanup: ƒ}
_cleanup: ƒ ()
_observer:
next: (x) => {…}
__proto__: Object
_queue: ***undefined***
_state: "ready"
closed: (...)
__proto__: Object
Many thanks in advance.
For those who are experiencing the same behaviour. It was due to the fact that I had the owner in the auth section of the schema. Deleted the {allow: owner}, part and the subscriptions started to work immediately.
here is a working example of AWS Amplify Subscriptions:
import Amplify from 'aws-amplify';
import API from '#aws-amplify/api';
import PubSub from '#aws-amplify/pubsub';
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
API.configure(awsconfig);
PubSub.configure(awsconfig);
// put above in root
// below is example
import { API, graphqlOperation } from 'aws-amplify';
var onAddNote = `subscription OnCreateNote {
onCreateNote {
id
patient {
id
organization {
id
}
}
}
}
`;
listenForNoteAdd() {
return API.graphql(graphqlOperation(onAddNote) ).subscribe({next: (noteData) => {
console.log("new note so reload consider reload")
let note = noteData.value.data.onCreateNote
console.log(JSON.stringify(note))
// now that you have indication of something happening
// do what you must next
}})
}
I had the same problem with GraphQL. Thing is: we have to return Owner in mutation response in order to let Subscription know to whom to send this event.
removing {allow: owner} did worked for me but thats not the right way since we require it in order to have owner based access to data.
so the correct way i found is:
if subscription is:
subscription MySubscription {
onCreateuser(owner: "$userName") {
id
name
number
}
}
mutation should be:
mutation MyMutation {
createUser(input: {name: "xyz", id: "user123", number: "1234567890"}) {
id
name
number
owner
}
}
we must return owner in mutation's response in order to get the subscription to that event and all other properties as same as mutation response.
For those coming here experiencing this error, do not delete {allow: owner}. Allow owner ensures only a user authenticated with Cognito User Pool can run queries, mutations, etc.
It looks like the OP is using amplify codegen to generate his API service, and if you look the listener has param for owner. It's optional, but if your #auth is {allow: owner} it is required.
Additional note: Not sure if he is using the correct owner field stored in his datastore or not. If there is not already an owner field created (or a different field specified), it will create one with a unique uuid. So he could be passing the incorrect owner or none at all.
Run a simple call to get the owner...
export const GetOwner = async (id: string): Promise<GetOwnerQuery> => {
const statement = `query GetAppData($id: ID!) {
getAppData(id: $id) {
owner
}
}`;
const gqlAPIServiceArguments: any = {
id,
};
const response = (await API.graphql(graphqlOperation(statement, gqlAPIServiceArguments))) as any;
return <GetOwnerQuery>response.data.getAppData;
};
...and pass that in your subscription.
const { owner } = await GetOwner(id);
if (owner) {
this.apiUpdateListener$ = this.api.OnUpdateAppDataListener(owner).subscribe((data) => {
console.log('api update listener ===> ', data);
});
}

Create a Notification System Based on User Preferences

I am trying to develop a notification system, but I am not sure if I do certain parts correctly. To simplify the case I will use some generic naming.
How the system should work:
A registered user can subscribe for notifications based on chosen filters from a data table grid. ( for example, notify me when the quantity of an item is X, or have multiple filters set up like set1.slug.quantity > X, some_value = false and some_int = 52)
How I store such preferences:
Example object
"O:8:"stdClass":2:{s:9:"set1.slug";a:1:{s:3:"$eq";s:10:"item_slug1";}s:13:"set1.quantity";a:1:{s:4:"$gte";i:1;}}"
Generation simplified
$object = new \stdClass();
$object->{'set1.slug'} = ['$eq' => 'item_slug1'];
$object->{'set1.quantity'} = ['$gte' => 1];
$object = serialize($object);
It attaches also the user_id and all the data serialized from the form to a partial MongoDB raw query.
Database stored object - predefined filter set for a user. Md is an md5 hash of the object for easier access and edit.
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
| id | object | user_id | md |
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
| 1 | O:8:"stdClass":2:{s:9:"set1.slug";a:1:{s:3:"$eq";s:10:"item_slug1";}s:13:"set1.quantity";a:1:{s:4:"$gte";i:1;}} | 22 | d5003ba3227c4db3189827329815b053 |
+----+-----------------------------------------------------------------------------------------------------------------+---------+----------------------------------+
This is how I would use it - my vision of how it would work.
I would call findByFilterMD in the API parser in a loop, while the table Items is populated.
// MongoDB query ( items database )
protected function executeUserFilter($array)
{
$list = Items::raw(function ($collection) use (&$array) {
return $collection->find(
$array, ["typeMap" => ['root' => 'array', 'document' => 'array']])
->toArray();
});
return $list;
}
// MySQL query - stored filters
protected function findByFilterMD($id)
{
$user = Auth::user();
$filter = PredefinedFilters::where('md', '=', $id)->first();
$deserialize = unserialize($filter->object);
$results = $this->executeUserFilter($deserialize);
// here would probably be a notification function like pusher or onesignal
}
I am aware that my attempt of achieving this might be totally wrong and I might reinvent the wheel since some tools might do that already.
Here is an example Item MongoDB object
{
"_id": {
"$oid": "5c0406abdc04e7007f17f4ef"
},
"name": "myObject",
"inner_ID": "0db0b19a-9c01-4c21-8e10-6879dbcb37f1",
"some_value": false,
"some_int": 52,
"set1": [
{
"slug": "item_slug1",
"quantity": 88,
"extra": {
"value": 0
}
},
],
"set2": [
{
"slug": "item_slug2",
"quantity": 88,
"extra": {
"value": 0
}
},
{
"slug": "item_slug3",
"quantity": 88,
"extra": {
"value": 0
}
}
],
"expires": "2018-12-02 22:21:30"
}
Here comes my questions
Is this way of doing it proper?
Where should the notification system kick in? I assume it might be in the place where I parse the api of items, then I should loop over the user filter data and run the stored object query - or should it be a separate system called with cron?
I am open to any suggestions, redesigns.
I developed an app kinda like this. My approach is to make use of Laravel notification found here:
https://laravel.com/docs/5.7/notifications#creating-notifications
Let's say, in your case, if someone modify/create data, the other users who subscribe will get notification.
Create notification
php artisan make:notification UserUpdateQuantity
Make User model notifiable, also create scope that subscribe for something
<?php
namespace App;
use Illuminate\Notifications\Notifiable;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use Notifiable;
public function scopeSubscribe($query)
{
return $query->where('subscribe', true);
}
}
the $query in method scopeSubscribe needs to be adjusted based on your business logic
Send notification
$subscriber = User::subscribe()->get();
Notification::send($subscriber, new UserUpdateQuantity());
Create event & listener
You can find the event and listener here https://laravel.com/docs/5.7/events
In the EventServiceProvider
protected $listen = [
'App\Events\QuantityUpdated' => [
'App\Listeners\SendUpdateQuantiryNotification',
],
];
Then run the command php artisan event:generate
Event listener
In the event listener, we send notification
public function handle(QuantityUpdated $event)
{
$subscriber = User::subscribe()->get();
Notification::send($subscriber, new UserUpdateQuantity());
}
Eloquent event
Add event on your eloquent model, so when someone update quantity, it triggers event and the listener will send notification to subscribed users
// In your model
protected $dispatchesEvents = [
'updated' => App\Events\QuantityUpdated::class
];

Apollo Normalization with dataIdFromObject not updating

I am having trouble with getting React+Apollo to update the store after I send a delete mutation. I am using the reactQL boiler plate which has apollo+react built in and an express graphQL server (I didn't install the apollo server- I just use the reference express-graphQL package). My data is stored in the mongoDB with a _id, but the actual data on the client side uses id as the id.
The apollo client is defined like this:
new ApolloClient(
Object.assign(
{
reduxRootSelector: state => state.apollo,
dataIdFromObject: o => o.id
},
opt
)
);
I have a parent component which uses
import courses from 'src/graphql/queries/courses.gql
#graphql(courses)
export default class CoursesPage extends Component {
constructor(props){
super(props)
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(event) {
this.props.mutate({ variables: {id: selectedId}}
.catch(err => console.log(err))
}
render() {
return (
{ this.props.data.courses.map(course =>
<CourseDelete selectedId={course.id} key={course.id} />
})
}
)
}
}
and a child component that looks like:
import deleteCoursefrom 'src/graphql/mutations/deleteCourse.gql
#graphql(deleteCourse)
export default class CourseDelete extends Component {
constructor(props){
super(props)
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(event) {
this.props.mutate({ variables: {id: this.props.selectedId}}
.catch(err => console.log(err))
}
render() {
return (
<button onClick={this.handleDelete}>Button</button>
)
}
}
where deleteCourse.gql:
mutation deleteCourse($id: String!) {
deleteCourse(id: $id) {
id
}
}
and my original query is in courses.gql:
query courses {
courses {
id
}
}
Apollo's dataIdFromObject is used to update objects already in the cache. So if you have a record and an ID, and you change other pieces of data against that same ID, React components listening to the store can can re-render.
Since your deleteCourse mutation seems to return the same ID, it still exists in the cache. Your store doesn't know it needs deleting- it just updates the cache with whatever data comes back. Since this mutation likely returns the same ID, there's nothing to signify that this should be removed.
Instead, you need to specify an update function (link goes to the official Apollo docs) to explicitly delete the underlying store data.
In my new ReactQL user auth example, I do the same thing (see the pertinent LOC here) to 'manually' update the store after a user logs in.
Since components are initially listening to a 'blank' user, I cannot rely on dataObjectFromId to invalidate the cache, since I'm starting with no users and therefore no IDs. So I explicitly overwrite store state manually, which triggers re-rendering of any listening components.
I explain the concept is the context of the above user auth in a YouTube video - this is the piece that's relevant: https://youtu.be/s1p4R4rzWUs?t=21m12s