Meteor: Publish using users profile properties rather than ID - mongodb

I'm currently creating an app that will be used by multiple companies.
Each user has the following profile:
username: johnDoe
emails: [{address: "some#email.com", verified: true}],
profile: {
name: "John Doe",
companyId: "1234"
}
I then have a collection (called Companies) of company objects that contain configuration info, templates etc specific to that company.
{
id: "1234",
configuration: {},
templates: []
}
In order to isolate each companies data I want to only publish data that matches the users profile companyId to the companies id.
if (Meteor.isServer) {
// collection to store all customer accounts
Companies = new Mongo.Collection('Companies');
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: Meteor.user().profile.companyId});
})
}
This currently works if I hardcode the clinic Id
// publish collection
Meteor.publish("Company", function () {
return Companies.find({id: "1234");
})
But returns an empty cursor with the Meteor.user().profile.companyId.
This means that the issue is either that I'm using the wrong function or more probably, the publish is happening before the user().profile.companyId can run.
Anybody know what I'm doing wrong? and do you have any advice on what to read up about so that I have an understanding of this moving forwards?
Thanks

Try doing an explicit findOne() in your publish function:
// publish collection
Meteor.publish("Company", function () {
var user = Meteor.users.findOne({_id: this.userId});
if(user && user.profile && user.profile.companyId) {
return Companies.find({id: user.profile.companyId});
} else {
console.log(user);
return this.ready();
}
});

Related

Discord.js/Mongodb How to update all database values

I am currently creating an economy bot for Discord, and i have a problem i don't know how to solve:
All users have a salary, that will be set with a command. I want to create a command that can update all users account with the set salary amount.
Instead of having to ping a specific user, how could i make it that the command would update values of all found users in the MongoDB Database?
Here is current code:
const profileModel = require("../models/profileSchema");
module.exports = {
name: 'pay',
aliases: [],
permissions: ["ADMINISTRATOR"],
description: "pay users their salary",
async execute(message, args, cmd, client, discord, profileData) {
const amount = profileData.pay;
const target = message.mentions.users.first();
try{
await profileModel.findOneAndUpdate({
userID: target.id,
}, {
$inc: {
bank: amount,
},
}
);
return message.channel.send(`Users have been paid.`);
} catch(err) {
console.log(err);
}
},
};
As you can see, currently its waiting for the user to ping a user. But i would want it to just update all found users inside the Database without needing to specify who it is.
I would really appereciate help!
Mongo has the method updateMany, docs found here, which updates all documents if the query is set to an empty object. So, just try:
await profileModel.updateMany({}, {
$inc: {
bank: amount,
},
}
);

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);
});
}

Updating current user's email address in Meteor

I'm having a problem updating the current user's email address in Meteor. The problem code is here (it's all client code):
var id = Meteor.userId();
if (firstName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.firstName": firstName }}
)};
if (lastName) {
Meteor.users.update(
{ _id: id },
{ $set: { "profile.lastName": lastName }}
)};
var oldEmail = Meteor.user().emails[0].address;
if (newEmail) {
Meteor.users.update(
{ _id: id, 'emails.0.address': oldEmail },
{ $set: { 'emails.0.address': newEmail }}
)};
The first two lines work fine (user can update their first and last names), but the last update fails with the following error showing in the console:
"errorClass {isClientSafe: true, error: 403, reason: "Not permitted. Untrusted code may only update documents by ID."
I don't understand the error, because I AM updating by ID--or at least I think I am.
Also: if I remove the reference to the old email, I get a simple "Update failed. Access denied" error in the console instead of the above-mentioned error.
Is there a way to fix this with client-side code only?
(I realize I'll also need to reset the "verified" key back to false, but that's a different issue I guess)
Do not edit the emails array directly. The Accounts package has utility functions for this use case, see Accounts.addEmail and Accounts.removeEmail.
First you will need to invoke a Method since these functions must be performed on the server.
Client js:
if (newEmail) {
Meteor.call('updateEmail', newEmail, err => {
if (err) console.log(`Error updating email address: {err}`);
});
}
server:
Meteor.methods({
updateEmail(newAddress) {
const userId = this.userId;
if (userId) {
const currentEmail = Meteor.users.findOne(userId).emails[0].address;
Accounts.addEmail(userId, newAddress);
Accounts.removeEmail(userId, currentEmail)
}
return;
}
});
You should also validate in your method that the new email address is a string that has an email address pattern.

How to update (override) a collection association's value in sails.js?

I have a model that has an attribute that is a collection association:
Take for example, a User model below.
module.exports = {
attributes: {
pets: {
collection: 'pet'
}
}
}
I am aware that I can add pets to a user instance with
user.pets.add(3);
But how could I replace any existing pets with a new group of pets??
Ok I've been playing with the API and found an answer. The following call should update (set) the pets association for a single user. If there were existing pets, this approach would override them.
User.update({id:1}, {pets: [{id: 7}, {id: 8}]}).exec(cb);
You'd remove all the existing pets and create new ones. sails.js has no single special API function to do what you are trying to do, but it's pretty simple either way:
var newPets = [
{ name: 'fluffy', user: 1 },
...
];
Pet.destroy({ user: 1 })
.then(function () {
return _.map(newPets, Pet.create);
})
.then(function (pets) {
// pets are "replaced"
});
Or something like that.

Accessing services field of Meteor.users

When I query Meteor.users I do not receive the services field or any other custom fields I have created outside of profile. Why is it that I only receive _id and profile on the client and how can I receive the entire Meteor.users object?
Thanks.
From the DOcs
By default, the current user's username, emails and profile are published to the client. You can publish additional fields for the current user with:
As said above If you want other fields you need to publish them
// server
Meteor.publish("userData", function () {
if (this.userId) {
return Meteor.users.find({_id: this.userId},
{fields: {'services': 1, 'others': 1}});
} else {
this.ready();
}
});
// client
Meteor.subscribe("userData");
The above answer does work, but it means you have to subscribe to said data, which you should do if you are getting data from users other than the currently logged in one.
But if all you care about is the logged in user's data, then you can instead use a null publication to get the data without subscribing.
On the server do,
Meteor.publish(null, function () {
if (! this.userId) {
return null;
}
return Meteor.users.find(this.userId, {
fields: {
services: 1,
profile: 1,
roles: 1,
username: 1,
},
});
});
And this is actually what the accounts package does under the hood