Displaying rethinkDB tables in real time in Phoenix Framework - real-time

I'm attempting to go a step further from my previous question regarding tables from RethinkDB in Phoenix.
Now I'm attempting to retrieve them over a channel in order to display in real-time, new insertions to the table.
I'm already inserting into the table through the handle_in function:
def handle_in("new_person", %{"firstName" => firstName, "lastName" => lastName}, socket) do
broadcast! socket, "new_person", %{firstName: firstName, lastName: lastName}
table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
{:noreply, socket}
end
And in the app.js:
dbInsert.on("click", event => { //Detect a click on the dbInsert div (to act as a button)
//Use a module from the channel to create a new person
chan.push("new_person", {firstName: firstName.val(), lastName: lastName.val()});
// Clear the fields once the push has been made
firstName.val("");
lastName.val("");
});
chan.join().receive("ok", chan => {
console.log("Ok");
});
Which function should I use to handle the:
table("users")
|> RethinkExample.Database.run
And how should I render the data if it's now a channel and not html?
I can render the inserted person with HTML+Javascript but what I want is to retrieve the new user from the DB and render it with my other table results in real-time.
Here's how I'm at visually speaking:
users.html.eex
<div class="jumbotron">
<div id="userList">
<%= for %{"first_name" => first_name, "last_name" => last_name} <- #users.data do %>
<p><%= "#{first_name} #{last_name}" %>!</p>
<% end %>
</div>
</div>
<div class="dbOperation">
First name: <input type="text" id="firstName"><br>
Last name: <input type="text" id="lastName"><br>
<div id="dbInsert">Insert</div>
<br>
<div id="userToInsert">User to insert: </div>
</div>
user_controller.ex
defmodule RethinkExample.UsersController do
use RethinkExample.Web, :controller
use RethinkDB.Query
def users(conn, _params) do
# List all elements of a table from the database
q = table("users")
# Query for filtering results:
# |> filter(%{last_name: "Palmer"})
|> RethinkExample.Database.run #Run the query through the database
render conn, "users.html", users: q #Render users searched on the users template
end
end
people_channel.ex
defmodule RethinkExample.PeopleChannel do
use Phoenix.Channel
use RethinkDB.Query
#Handles the insert subtopic of people
def join("people:insert", auth_msg, socket) do
{:ok, socket}
end
# handles any other subtopic as the people ID, ie `"people:12"`, `"people:34"`
def join("people:" <> _private_room_id, _auth_msg, socket) do
{:error, %{reason: "unauthorized"}}
end
def handle_in("new_person", %{"firstName" => firstName, "lastName" => lastName}, socket) do
broadcast! socket, "new_person", %{firstName: firstName, lastName: lastName}
query = table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
new_person = %{"id": hd(query.data["generated_keys"]), "firstName": firstName, "lastName": lastName}
broadcast! socket, "new_person", new_person
{:noreply, socket}
end
def handle_out("new_person", payload, socket) do
push socket, "new_person", payload
{:noreply, socket}
end
end
app.js
import {Socket} from "phoenix"
let App = {
}
export default App
// Fetch fields from HTML through Jquery
let firstName = $("#firstName")
let lastName = $("#lastName")
let dbInsert = $("#dbInsert")
let userToInsert = $("#userToInsert")
let userList = $("#userList")
let socket = new Socket("/ws") //Declare a new socket
socket.connect() //Connect to the created socket
let chan = socket.chan("people:insert", {}) //Assign the people insertion channel to the socket
dbInsert.on("click", event => { //Detect a click on the dbInsert div (to act as a button)
//Use a module from the channel to create a new person
chan.push("new_person", {firstName: firstName.val(), lastName: lastName.val()});
// Clear the fields once the push has been made
firstName.val("");
lastName.val("");
})
chan.on("new_person", payload => {
userToInsert.append(`<br/>[${Date()}] ${payload.firstName} ${payload.lastName}`);
console.log("New Person", payload);
userList.append(`<br><p> ${payload.firstName} ${payload.lastName}!</p>`);
})
chan.join().receive("ok", chan => {
console.log("Ok");
})

You need a handle_out function in your channel that you listen to for the insert. If you use broadcast_from! then the sender will be excluded, if you use broadcast! then the sender will also receive the message.
Add the following to your channel:
def handle_out("new_person", payload, socket) do
push socket, "new_person", payload
{:noreply, socket}
end
And the following to your JS client:
chan.on("new_person", payload => {
console.log("New Person", payload);
});
The Channel documentation is available at http://www.phoenixframework.org/docs/channels
edit
When inserting a record in Rethink - the output looks like:
%RethinkDB.Record{data: %{"deleted" => 0, "errors" => 0,
"generated_keys" => ["7136199a-564b-42af-ad49-5c84cbd5b3e7"],
"inserted" => 1, "replaced" => 0, "skipped" => 0, "unchanged" => 0}}
We know that the data you get back from a rethink query looks like:
{"first_name" => "John",
"id" => "57c5d0d2-5285-4a24-a999-8bb7e2081661", "last_name" => "Smith"},
So - to broadcast the new record to the browser, we want to replicate this data structure so if you change your handle_in function to:
def handle_in("new_person", %{"firstName" => first_name, "lastName" => last_name}, socket) do
query = table("users")
|> insert(%{first_name: firstName, last_name: lastName})
|> RethinkExample.Database.run
new_person = %{"id": hd(query.data["generated_keys"]), "first_name": first_name, "last_name": last_name}
broadcast! socket, "new_person", new_person
{:noreply, socket}
end
Then - if you follow the steps above with the handle_out and chat.on then you will see the person logged out in your JavaScript console. From there - you can append it to your DOM using Javascript.

Related

How to add current user id to the axios.get request?

how can I add current user id to the axios.get request url in the front end code?
here are my codes;
backend: urls.py
urlpatterns = [
path('<int:pk>/', UserDetail.as_view()),
]
and views.py
class UserDetail(APIView):
permission_classes = [AllowAny]
http_method_names = ['get', 'head', 'post']
"""
Retrieve, update or delete a user instance.
"""
def get_object(self, pk):
try:
return NewUser.objects.get(pk=pk)
except NewUser.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
user = self.get_object(pk)
serializer = CustomUserSerializer(user)
return Response(serializer.data)
frontend:
useEffect(() => {
if (localStorage.getItem("access_token")) {
axiosInstance.get('users/**???**').then((obj) => {
{
setname(obj.username)
setemail(obj.email)
setidx(obj.uid)
}
console.log(obj);
console.log(obj.data);
setTimeout(() => {
props.resetProfileFlag();
}, 3000);
});
}
}, [props.success])
If I add user id manually (like say; axiosInstance.get('users/1').then((obj) => { ...) it gets the user details.
in your axios part you need to use params like these:
useEffect(() => {
if (localStorage.getItem("access_token")) {
axiosInstance.get('users/',
{
params: {
id: '1'
}
}).then((obj) => {
{
setname(obj.username)
setemail(obj.email)
setidx(obj.uid)
}
console.log(obj);
console.log(obj.data);
setTimeout(() => {
props.resetProfileFlag();
}, 3000);
});
}
}, [props.success])
and in the backend side you can get the data also from the request.params .
Thank you Rassaka, however, still I can't get a single user details, but I get the list of all users data at the console.
I moved to DRF Viewsets and HyperlinkedModelSerializers:
class UserSerializer(serializers.HyperlinkedModelSerializer):
posts = serializers.HyperlinkedRelatedField(
many=True,
queryset=Post.objects.all(),
view_name='blog:post-detail',
)
url = serializers.HyperlinkedIdentityField(view_name='users:user-detail')
class Meta:
model = User
fields = ('url', 'id', 'user_name', 'email', 'posts')
views.py :
class UserViewSet(viewsets.ReadOnlyModelViewSet):
"""
This viewset automatically provides `list` and `detail` actions.
"""
permission_classes = [AllowAny]
queryset = User.objects.all()
serializer_class = UserSerializer
#lookup_field = 'pk'
def post(self, request):
try:
refresh_token = request.data["refresh_token"]
token = RefreshToken(refresh_token)
token.blacklist()
return Response(status=status.HTTP_205_RESET_CONTENT)
except Exception as e:
return Response(status=status.HTTP_400_BAD_REQUEST)
urls.py:
urlpatterns = [
router.register(r'users/<int:pk>', views.UserViewSet),
router.register(r'users', views.UserViewSet),
]
urlpatterns = [ re_path(r'^', include(router.urls)) ]
and finally my react frontend:
const UserProfile = props => {
const [data, setData] = useState({
users: [],
});
useEffect(() => {
if (localStorage.getItem("access_token")) {
axiosInstance.get('users/', {params: { id: '1'}}).then((res) => {
setData({
users: res.data,
})
console.log(res.data);
setTimeout(() => {
props.resetProfileFlag();
}, 3000);
})
.catch(err =>{
console.log(err)
})
}
}, [setData], [props.success])
return (
<React.Fragment>
<div className="page-content">
<Container fluid>
<Row>
<Col lg="12">
<Card>
<CardBody>
<div className="d-flex">
<div className="ms-3">
<img
src={data.users.avatar}
alt=""
className="avatar-md rounded-circle img-thumbnail"
/>
</div>
<div className="align-self-center flex-1">
<div className="text-muted">
<h5>Username: {data.users.user_name} {''}</h5>
<p className="mb-1">Email: {data.users.email} {''}</p>
<p className="mb-0">Id no: {data.users.id} {''}</p>
</div>
</div>
</div>
</CardBody>
</Card>
</Col>
</Row>
</Container>
</div>
</React.Fragment>
)
}
export default UserProfile
The issue is; after login I get the right user data in the console, however, when I go to the user profile page, firstly I get this error message "GET http://127.0.0.1:8000/api/users/?id=1 401 (Unauthorized)" in the console and of course in the backend terminal. But immediate after that the backend refreshs the token ""POST /api/token/refresh/ HTTP/1.1" 200" -> "GET /api/users/?id=1 HTTP/1.1" 200". But I get all user Arrays(data) - (not the logged-in user data) in the console, however, the user profile page cannot retrieve any user data. So I can not understand if user data cannot be retrieved to the profile page because the axiosInstanse refreshes token after login or because my frontend design is wrong. Or something is wrong with the backend?? Please, your advices? ...

Socket.io 4.x - How do I fetch users as username lists?

First, the client enters the room name and user name. And I'm sending them to the server.
Client
socket.on('connect', () => {
socket.emit("join", { roomID: roomID, name: name });
})
Then I take it on the server and put it in the room.
Server console.log(room) returns {roomID: "1", name: "user"}, so it's good
io.on('connection', socket => {
socket.broadcast.emit('a user connected')
socket.on('join', (room) => {
console.log(room);
socket.join(room.roomID);
});
})
Now I need to send the list of users in this room back to the client.
However
How do I use the username when doing socket.join?
How do I send the users in this room to the client as a list? (I need usernames)
Thanks.
Assuming you have kept the list of users somewhere, you can use a call back
Client
socket.on('connect', () => {
socket.emit("join", { roomID: roomID, name: name }, (data) => {
//This is the function that's called below using callback.
});
})
SERVER
socket.on('join', (room, callback) => {
console.log(room);
socket.join(room.roomID);
callback(/* array of users here */)
});

Broadcast to different channel Phoenix 1.1.6

I'm trying to broadcast to a different channel in my app but I can not get it to work. Also I trying to write a test but I'm not sure how.
From what I can gather I succeed in broadcasting the message from the notification_channel but it's not received in chat_channel.
Notification should send to chat.
notification_channel.ex
def handle_in("new:group:recommendation", msg, socket) do
payload = %{
message: msg["message"],
url: msg["url"],
title: msg["title"],
user_name: get_name_of_user(socket.assigns.user_grapqhl_id),
user_grapqhl_id: socket.assigns.user_grapqhl_id
}
IO.puts "incomming"
IO.inspect msg
Enum.map(msg["groups"], fn(x) ->
App.Endpoint.broadcast_from! self(), "chat:"<>x,
"new:recommendation", payload
end)
{:reply, :ok, socket}
end
chat_channel.ex
def handle_in("new:recommendation", msg, socket) do
IO.puts "i am a recommendation !"
IO.inspect msg
chat_msg = %{
"creator_id" => msg["user_grapqhl_id"],
"text" => msg["message"],
"creator_name" => msg["user_name"]
}
broadcast! socket, "new:msg", create_chat_msg(chat_msg,socket)
{:reply, :ok, socket}
end
test
test "do we send a new:recommendation to chat ?", %{guardian_token: guardian_token} do
nils_base_64 = Base.encode64("user:nils")
{:ok, socket} = connect(UserSocket, %{})
{:ok, _, socket1} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
{:ok, _, socket} = subscribe_and_join(socket1, "chat:Y2hhdDpjaGF0Mw==", %{"guardian_token" => guardian_token})
payload = %{
"message" => "look at this cool thing!",
"url" => "link to stuff",
"title" => "AWESOME EVENT",
"groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
}
reply = %{message: "look at this cool thing!", title: "AWESOME EVENT", url: "link to stuff", user_grapqhl_id: nils_base_64, user_name: "Nils Eriksson"}
ref = push socket1, "new:group:recommendation", payload
assert_reply ref, :ok
assert_broadcast "new:recommendation", ^reply
end
This test passes and i can get it to fail by changing the reply
or commenting out the broadcast. I can not get it to fail by changing the handle_in to receive fail:please in chat_channel.
That is something that it would complain on if i would send change this
ref = push socket1, "new:group:recommendation", payload
to ref = push socket, "new:group:recommendation", payload not supprising in that case.
This is what is on the wire.
Process mailbox:
%Phoenix.Socket.Message{event: "init:msgs", payload: %{messages: []}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}
%Phoenix.Socket.Broadcast{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, topic: "chat:Y2hhdDpjaGF0Mw=="}
%Phoenix.Socket.Message{event: "new:recommendation", payload: %{message: "look at this cool thing!", title: "AWESOME EVENTs", url: "link to stuff", user_grapqhl_id: "dXNlcjpuaWxz", user_name: "Nils Eriksson"}, ref: nil, topic: "chat:Y2hhdDpjaGF0Mw=="}
I use channel authentication since the elm package i use does not support authentication on socket level yet. so this is what it looks like in chat
def join("chat:" <> chat_id, %{"guardian_token" => token}, socket) do
IO.puts chat_id
case sign_in(socket, token) do
{:ok, authed_socket, _guardian_params} ->
Process.flag(:trap_exit, true)
send(self, {:after_join})
[_type, node_chat_id] = Node.from_global_id(chat_id)
{:ok, assign(authed_socket, :chat_id, node_chat_id)}
{:error, reason} ->
IO.puts "Can't join channel cuz: " <> reason
# handle error TODO
end
end
Since you use broadcast_from/4 from your Endpoint.You should use handle_info/2 in your chat_channel:
alias Phoenix.Socket.Broadcast
...
def handle_info(%Broadcast{topic: _, event: ev, payload: payload}, socket) do
IO.puts ev
IO.inspect payload
# do something with ev and payload( push or broadcast)
{:noreply, socket}
end
Or you can listen that event from your client:
chatChannel.on("new:recommendation", resp => {
// doSomething with response
}
Edit:
Let's explain a bit about how the channel and PubSub system work.
When you want to broadcast or push an event with payload.First it will send to the PubSub system and then the PubSub system will send it to all the subscriber process (channel) with the topic that channel registered itself with the PubSub system.
And when you use Endpoint.broadcast_from/4 to broadcast an event from your server.The PubSub system will receive an event with payload and broadcast that event to the topic that channel registered.
The channel will trigger handle_out callback and push the message to the client.
So in your chat_channel you don't need to handle_in "new:recommendation" event.Your client just need to listen to that event.
chatChannel.on("new:recommendation", resp => {
// do something with response
}
And let me rewrite your test:
setup do
nils_base_64 = Base.encode64("user:nils")
{:ok, socket} = connect(UserSocket, %{})
{:ok, _, socket} = subscribe_and_join(socket, "notifications:"<>nils_base_64, %{"guardian_token" => guardian_token})
{:ok, socket: socket}
end
test "do we send a new:recommendation to chat ?", %{socket: socket} do
MyApp.Endpoint.subscribe("chat:Y2hhdDpjaGF0Mw==")
payload = %{
"message" => "look at this cool thing!",
"url" => "link to stuff",
"title" => "AWESOME EVENT",
"groups" => ["Y2hhdDpjaGF0Mw==", "Y2hhdDpwdWJsaWM="]
}
reply = %Phoenix.Socket.Broadcast{message: "look at this cool thing!",
title: "AWESOME EVENT",
url: "link to stuff",
user_grapqhl_id: nils_base_64,
user_name: "Nils Eriksson"}
ref = push socket, "new:group:recommendation", payload
assert_reply ref, :ok
assert_receive ^reply
end
By subscribe to the topic that you want to listen you can make sure that your channel is received the message with assert_receive.
That is the way to test broadcast to a different channel.
Give it a try and tell me.The test will pass.
Use App.Endpoint.broadcast topic, event, payload with the topic of your chat channel. It should work.

Openerp send email to multiple recipients

I am using a template to send an email. I want to send the email to multiple recipients, but in the field email_to accept only object attributes:
<field name="email_to">${object.attribute}</field>
I want to send email using the same template to multiple recipients.
I am using below code:
def action_send_email_to_attendees(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
ir_model_data = self.pool.get('ir.model.data')
try:
template_id = ir_model_data.get_object_reference(cr, uid, 'mymodule', 'mymodule_invitation_email')[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
ctx = dict(context)
ctx.update({
'default_model': 'mymodule.module',
'default_res_id': ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
'mark_so_as_sent': True
})
return {
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
}

How to catch Execution exception in scala playframework?

I'm getting Execution exception
[APIError: You must provide an email address in order to create a ticket.]
it is possible to catch this error and post only
"You must provide an email address in order to create a ticket."
this error should showup in the view page.
the code that give error :
val question = uservoice.post("/api/v1/tickets.json", ticket).getJSONObject("ticket")
Controller:
def contactSave = withOptionUser { user => implicit request =>
contactForm.bindFromRequest.fold(
formWithErrors => BadRequest(html.anon.contact(user, formWithErrors)),
c => {
val uservoice = new com.uservoice.Client(SUBDOMAIN, API_KEY, API_SECRET)
val ticketMsg = Map("state" -> "open","subject" -> c._2, "message" -> c._3).toMap[String,Object].asJava
val ticket = Map("email" -> c._1, "ticket" -> ticketMsg).toMap[String,Object].asJava
Logger.debug(ticket.toString)
val question = uservoice.post("/api/v1/tickets.json", ticket).getJSONObject("ticket")
Logger.debug(question.toString)
Ok(views.html.anon.contactThanks(user))
}
)
}
Html :
#main("Contact Us",user,"contact",stylesheet, scripts) {
#helper.form(routes.UservoiceController.contactSave) {
<section class="contact">
<div class="contactBox contentBox">
<div class="leftColumn">
<h1>Contact Us</h1>
<span>You can fill out this form for any general inquiries, comments, etc.</span>
<span>You can also find us on Facebook and Twitter!</span>
<div class="social">
#form.globalError.map { error =>
<span class="error" data-xpl="loginError">
#error.message
</span>
you can use a try / catch block and create a form
var form = contactForm.bindFromRequest()
form.fold(
formWithErrors => ...
c => {
...
try {
val question = uservoice.post("/api/v1/tickets.json", ticket).getJSONObject("ticket")
Logger.debug(question.toString)
Ok(views.html.anon.contactThanks(user))
} catch {
case e: Exception =>
Logger.error("error ...", e)
val formWithError = form.withError("email", "You must provide an email address in order to create a ticket.")
BadRequest(html.anon.contact(user, formWithError))
}
}
)