cult-of-coders / grapher

Grapher: Meteor Collection Joins + Reactive GraphQL like queries
https://atmospherejs.com/cultofcoders/grapher
MIT License
275 stars 53 forks source link

Dynamic Links\Reducers #437

Closed odesey closed 3 years ago

odesey commented 3 years ago

Thanks for this great package, it's been a life saver!

The title might be a little miss-leading, but here is the problem I have.

I have two collections, Threads and Messages Threads is basically a meta-data document that has information about the messages Messages has the actual data displayed to the user, and each message has a userId field (important)

When I retrieve the messages that belong to a thread, I have a link that looks like this:

Messages.addLinks({
    user: {
        type: 'one',
        collection: Meteor.users,
        field: 'userId'
    }
});

I also have a reducer that looks like this:

Meteor.users.addReducers({
    name: {
        body: {
            profile: {
                firstName: 1
            }
        },
        reduce(object) {
            const { profile } = object;

            return profile.firstName;
        }
    }
});

Finally I have my query that looks like this:

const query = Messages.createQuery(
            {
                $filters: {
                    threadId
                },
                $options: {
                    sort: { createdAt: -1 }
                },
                lastUpdate: 1,
                createdAt: 1,
                text: 1,  //message text
                user: {
                    name: 1
                }
            }
        );
        return query.fetch();

Each thread and it's messages are accessible by 2 parties. The user who created the thread (public), and the business that the message was sent to. Everything works great except for one thing I would like to change.

When a business loads the messages, the app shows the names of everyone who created a message in the thread, public user and employees in the business.

This is the problem I am having here: When the user (public) loads the messages, I want the messages sent from the business to have the word Business instead of the user's real name's in the name field. Maybe a dynamic Link or Reducer?

Just for clarity: Business should see: javascript {text: 'message text here', name: {firstName: 'John'}

Public user should see: javascript {text: 'message text here', name: {firstName: 'Business'} NOT javascript {text: 'message text here', name: {firstName: 'John'}

Is this something I can do easily with the grapher package?

Thanks.

vparpoil commented 3 years ago

To do this securely (i.e. not sending the real username of your business user to the client), you need to call the grapher query from a method. This method will use this.userId to determine the status of the user : Public user or Business. Then, for the business user, return the query results as is and for the public user you either use a different query (safer) or you update the data before sending it back to the client.

odesey commented 3 years ago

Thanks for responding @vparpoil

I am already using two different methods, one for the public and one for the business and two different queries as well. The business query works perfectly, I am not sure how to configure the public query to send back Business for only the messages that do not match the this.userId

The query in my initial question is the business query, how would I modify it to change the documents on the fly that do not match the this.userId check?

Thanks.

vparpoil commented 3 years ago

If you can access the userId of the user querying data inside the reducer, you can achieve what you want (ie: the reducer will compute a different value based on the user identity). Apparently this.userId is unavailable, but have you tried using Meteor.userId() ?

Other way to deal with this would be to alter the results of the fetch() in your method before returning the array of results to the client

[Edit] Other answer : Use Params-aware reducers => see here in grapher doc, and pass the curent userId as a param of the query.

And some more context :

Meteor.users.addReducers({
    name: {
        body: {
            _id: 1,
            profile: {
                firstName: 1
            }
        },
        reduce(object, params) {
            if (isPublicUser(params.userId) && object._id !== params.userId){
                   return "Business"
            }
            return object.profile.firstName;
        }
    }
});
odesey commented 3 years ago

Thanks @vparpoil this worked and is exactly what I was looking for, very much appreciated!