Implementing Pusher Chat in Rails and React - ruby-on-rails

I'm using this repo to create a chat system between 2 users in a Rails and React project. I've been able to log the user input into the console, and I have created messages_controller and message_threads_controller according to the repo.
However, I'm unable to persist the message to Rails db and then authenticate a user with Pusher before sending it to Pusher. Mainly because the from_uid, to_uid and thread_uid are not present by the time the message is been sent to Rails. Sending the message to rails like this:
sendMessage = (event) => {
event.preventDefault();
const {message} = this.state;
axios.post('/api/v1/messages', {message: message})
.then((response) => {
console.log(response);
})
console.log('send Message')
this.setState({'message': message});
console.log(this.state.message);
}
In my routes.rb file I have this
resources :messages
get 'threads/:id', to: 'message_threads#index'
post '/pusher/auth', to: 'pusher#auth'
I'm missing some required parameters, this is the error I get.
Pusher::Error - Bad request: Missing required parameter:
The flow according to this tutorial is that the message needs to be persisted first by the rails database before sending it to Pusher.
My question now is how do I produce the extra parameters (from_uid, thread_uid, to_uid) being used on the React side of the app here, to enable messages to be created?
Also, how do I authenticate the user using Pusher?
According to this Stack Overflow link they are getting from Rails the CSRF value like this - csrf = $('meta[name=csrf-token]').attr('content'). But I could not implement the same in React.

Answer from the author of the git repo.
The example I created here was pretty bare bones but below are a few bullet points that I hope will explain how you could expand on it.
Add a User model and Thread model to the Rails app
When a User is created, generate a public UID for the user (you can use Ruby's built-in SecureRandom.uuid to generate the id) and save that to the DB. This would become the ID for that user that you would expose in your javascript to allow for communications between users. All users would have a UID.
When a Thread is Created, generated a thread UID this would become the unique id for the conversation taking place
Add a Users_Threads has_and_belongs_to_many relationship so that you can eventually track the users that are subscribed to which threads
When React app loads, use an Ajax request to get a list of the current User's friends (returns list of user details + their uid) and a request to get all threads for current User
So let's say for example a User named Fred clicked on a User named Bob and wanted to send Bob a message but they do not currently have a thread. Fred types the message, clicks Submit and you send an Ajax request containing the message text, from_uid (Fred) and to_uid (Bob) with thread_uid as null (since there is no existing convo and no existing thread).
Your Rails app then receives that requests at a controller and sees that Fred is trying to send Bob a message and the thread ID is null, so the controller create a new thread (with its own UID) and then add two entries to users_threads one for the new thread_uid and bob's uid and another for the new thread_uid and Fred's uid. After that, you'd create a Message with that thread_uid and the participant details.
You'd also probably want users to see that they are part of a new thread without having to reload the page so you'd I think you'd want a Pusher channel subscription just for notifying users of a new thread. So I'd say in the UserThreads model after create a new thread you could send something like Pusher.trigger('threads_channel', user_secret_uid, { thread: new_thread_uid }). You'd also need to make sure in the react app that each user subscribes to the threads_channel for their user_secret_uid. For security, i'd make sure this is a different uid than the messaging otherwise people could subscribe to a list of a different users threads.

Related

How to change Rails object fields after it's been paid via Stripe Checkout?

In my application, a user can create a draft of an object, and then on a "finalise" view they can check if they only want the standard price or one of the two extra features as well (e.g. their entry being featured on the platform) - these are stored as three separate products each with their own price because I want to be able to track them separately in the future.
I am using Checkout for payments and my backend is Rails.
I am listening to webhooks, but I don't know how I could actually modify the object in question, since I can't see any webhook request body that contains the information I need.
What I imagine I'd need is something like this: "there has been a successful charge of $x related to your object with id 123, and the charge included product prod_asdf and prod_sdfg", and then I could update e.g. the paid and featured boolean fields of my object.
This seems like a no-brainer, since the products and prices are actually handled by Stripe and they are passed to the checkout session, I don't see why I can't access them from the webhooks?
EDIT: I'm wondering if passing necessary metadata to the Session object and then using the same metadata listening to a checkout.session.completed event is a good idea.
You can link a Checkout Session back to your Listing object by including the related object ID in the metadata (https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-metadata) when you create the Checkout Session. When you listen for checkout.session.completed events, the event will come with a Checkout Session object. You can easily link the Checkout Session back to your Listing object by checking the metadata and it also has an amount_total field so that will tell you the total payment amount.
You can get the products included in a session from line_items (https://stripe.com/docs/api/checkout/sessions/object#checkout_session_object-line_items). Unfortunately, line_items is not included in the webhook event by default since it is expandable (https://stripe.com/docs/expand). After receiving the checkout.session.completed event you can use the ID to retrieve the Checkout Session with line_items expanded like this:
session = Stripe::Checkout::Session.retrieve(
id: 'cs_test_xxx',
expand: ['line_items'],
)
line_items = session.line_items
Alternatively, you can just retrieve the Checkout Session's line items (https://stripe.com/docs/api/checkout/sessions/line_items) like this:
line_items = Stripe::Checkout::Session.list_line_items('cs_test_D3OF4MY1VGflR8idkewd795t8MLd4PwA3wxdLCHikdRFTIkBecH6FKij', {limit: 5})

Sending mail 'to' OpenStruct through mailer

I have an app where users can sign up for workshops and admin has a possibility to write an e-mail to all the participants through the app. The fragment of code to send mail message to the group looks like this
workshop.students_all.each do |user|
WorkshopNotifyGroupMailer.notify_user(user, workshop, subject, body).deliver_later
end
so it's nothing extraordinary (User and Workshops are instances of models).
Now, I wanted to add one additional e-mail address to be sent each time a group is notified (just to have a copy how does the sent mail look like). I thought of doing it something like that (to keep the code short):
admin = OpenStruct.new(email: 'admin#email.com', first_name: 'Nameless') #These are fields taken from User instance by mailer
WorkshopNotifyGroupMailer.notify_user(admin, workshop, subject, body).deliver_later
Unfortunately, I receive "Unsupported argument type: OpenStruct" error. Is there a way to send an e-mail which uses an instance of a model using some kind of artificial structure? (In this case just assume admin is not on the user list and won't be)

Temporary store an active record instance for notification view

I have a list of records on a notification panel, i've used the gem public activity to populate the panel with these notifications, when a user clicks on one instance i redirect the user to a custom URL that will give details on that notification,on this redirection, i change the read column of that notification in the activities table to true
def email_confirmed
# loads one specific activity/notification using the id from the url params
#notification = load_activities.find(params[:id])
# get the establishment,that is the trackable object,using activity id
# get the establishment using the 'trackable_id' field in the activities
# table
# Update the read column in the database on visit on the notification details
# view
#notification.update_columns(read: true)
#establishment_id = #notification.trackable_id
# use this instance of an establishment in the 'view all' action view
#establishment_details = Establishment.where(id:#establishment_id)
end
I have a AJAX script running that deleted the read notifications
setInterval(function(){
$.ajax({
url:'/notifications/remove_read_notifications',
type: "GET",
success: function(data){
console.log("in side delete");
}
});
},3000);
Now while im reading the notification table, i would like that if a user would refresh this page, it must not give an error that says the instance with "id" does not exist in the database, as there a way to circumvent this that will allow me to store that notification temporarily,help would be appreciated
The whole point of AJAX is for it to be asynchronous, but the behavior you are trying to accomplish is not consistent with that goal. Instead of using AJAX to delete the notifications use the notification's read status to dictate the presentation logic. If the user does not see it does not matter if the notification was deleted or not.

ruby on rails model method loop

The title is probably not the best, but here is my question. I have a rails model with this method
def stats(event_id)
while true do
sleep 1
response = HTTParty.get (xxx)
response
end
end
I continuously stream the response to the client and then I have a client script that calls this method whenever a user click on another event.
in my head, I think there is an issue. whenever the client script calls this method again, a new instance is created (in the controller) which means i'm streaming 2x to the client (the previous one and the new instance).
How can I get this to work? I need this instance to exist once for each user so no duplicate data is streamed and previous instances for the user should stop whenever a new request is made.
I don't mind moving this to the controller if it helps. Also, you access the streaming page only if you are logged in.
Thanks.

How to preserve session data through a User Registration

I use AuthLogic to activate / authenticate new users, and I am currently using session data so the system knows where to send the new user after they activate (such as which project to show them). So, it uses a number of variables such as session[:project_id], which need to be there when the user activates their account the first time.
The issue is this - what happens if the session data isn't there when the user activates? A few cases:
A user registers on one browser but then opens their activation e-mail on another,
A user registers / activates using incognito mode,
A user waits a day or two before activating their account.
From what I can tell, using session data presents a UX vulnerability because when it does not persist, the user ends up in an unhelpful location (or even worse, a 500 error due to expecting a session variable that does not exist).
It is in my mind necessary that information such as project_id (and a few other variables) are reliably stored from registration to activation. I am wary of creating new database fields for this limited and temporary purpose, but I am open to all suggestions. How can I close this loophole?
In response to Omar's answer:
In UserMailer.rb I have defined activation_instructions(user), which send out the activation_instructions e-mail to new users. At the moment, I see the following:
def activation_instructions(user)
#user = user
#account_activation_url = activate_url(user.perishable_token)
mail(:to => "#{user.login} <#{user.email}>", :subject => "Welcome!" )
end
How can I add the get parameters to this? Say I have the parameters project_id and category_id?
You could send that parameter as a get parameter via the activation email, that would solve all three problems, no?

Resources