Permalink
Browse files

Create socket and room channel

  • Loading branch information...
bnhansn committed Oct 22, 2016
1 parent b209f76 commit 1c1a39434f3ccc81fbc6d7d42f83b7cfcaae9856
@@ -0,0 +1,16 @@
defmodule Sling.Repo.Migrations.CreateMessage do
use Ecto.Migration
def change do
create table(:messages) do
add :text, :string, null: false
add :room_id, references(:rooms, on_delete: :nothing), null: false
add :user_id, references(:users, on_delete: :nothing), null: false
timestamps()
end
create index(:messages, [:room_id])
create index(:messages, [:user_id])
end
end
@@ -0,0 +1,18 @@
defmodule Sling.MessageTest do
use Sling.ModelCase
alias Sling.Message
@valid_attrs %{text: "some content"}
@invalid_attrs %{}
test "changeset with valid attributes" do
changeset = Message.changeset(%Message{}, @valid_attrs)
assert changeset.valid?
end
test "changeset with invalid attributes" do
changeset = Message.changeset(%Message{}, @invalid_attrs)
refute changeset.valid?
end
end
@@ -0,0 +1,17 @@
defmodule Sling.RoomChannel do
use Sling.Web, :channel
def join("rooms:" <> room_id, _params, socket) do
room = Repo.get!(Sling.Room, room_id)
response = %{
room: Phoenix.View.render_one(room, Sling.RoomView, "room.json"),
}
{:ok, response, assign(socket, :room, room)}
end
def terminate(_reason, socket) do
{:ok, socket}
end
end
@@ -1,37 +1,25 @@
defmodule Sling.UserSocket do
use Phoenix.Socket
## Channels
# channel "room:*", Sling.RoomChannel
channel "rooms:*", Sling.RoomChannel
## Transports
transport :websocket, Phoenix.Transports.WebSocket
# transport :longpoll, Phoenix.Transports.LongPoll
# Socket params are passed from the client and can
# be used to verify and authenticate a user. After
# verification, you can put default assigns into
# the socket that will be set for all channels, ie
#
# {:ok, assign(socket, :user_id, verified_user_id)}
#
# To deny connection, return `:error`.
#
# See `Phoenix.Token` documentation for examples in
# performing token verification on connect.
def connect(_params, socket) do
{:ok, socket}
def connect(%{"token" => token}, socket) do
case Guardian.decode_and_verify(token) do
{:ok, claims} ->
case Sling.GuardianSerializer.from_token(claims["sub"]) do
{:ok, user} ->
{:ok, assign(socket, :current_user, user)}
{:error, _reason} ->
:error
end
{:error, _reason} ->
:error
end
end
# Socket id's are topics that allow you to identify all sockets for a given user:
#
# def id(socket), do: "users_socket:#{socket.assigns.user_id}"
#
# Would allow you to broadcast a "disconnect" event and terminate
# all active sockets and channels for a given user:
#
# Sling.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
#
# Returning `nil` makes this socket anonymous.
def id(_socket), do: nil
def connect(_params, _socket), do: :error
def id(socket), do: "users_socket:#{socket.assigns.current_user.id}"
end
View
@@ -0,0 +1,17 @@
defmodule Sling.Message do
use Sling.Web, :model
schema "messages" do
field :text, :string
belongs_to :room, Sling.Room
belongs_to :user, Sling.User
timestamps()
end
def changeset(struct, params \\ %{}) do
struct
|> cast(params, [:text, :user_id, :room_id])
|> validate_required([:text, :user_id, :room_id])
end
end
View
@@ -5,6 +5,7 @@ defmodule Sling.Room do
field :name, :string
field :topic, :string
many_to_many :users, Sling.User, join_through: "user_rooms"
has_many :messages, Sling.Message
timestamps()
end
View
@@ -7,6 +7,7 @@ defmodule Sling.User do
field :password_hash, :string
field :password, :string, virtual: true
many_to_many :rooms, Sling.Room, join_through: "user_rooms"
has_many :messages, Sling.Message
timestamps()
end
View
@@ -0,0 +1,21 @@
export function connectToChannel(socket, roomId) {
return (dispatch) => {
if (!socket) { return false; }
const channel = socket.channel(`rooms:${roomId}`);
channel.join().receive('ok', (response) => {
dispatch({ type: 'ROOM_CONNECTED_TO_CHANNEL', response, channel });
});
return false;
};
}
export function leaveChannel(channel) {
return (dispatch) => {
if (channel) {
channel.leave();
}
dispatch({ type: 'USER_LEFT_ROOM' });
};
}
View
@@ -1,11 +1,26 @@
import { reset } from 'redux-form';
import { Socket } from 'phoenix';
import api from '../api';
import { fetchUserRooms } from './rooms';
const API_URL = process.env.REACT_APP_API_URL;
const WEBSOCKET_URL = API_URL.replace(/(https|http)/, 'ws').replace('/api', '');
function connectToSocket(dispatch) {
const token = JSON.parse(localStorage.getItem('token'));
const socket = new Socket(`${WEBSOCKET_URL}/socket`, {
params: { token },
logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data); }
});
socket.connect();
dispatch({ type: 'SOCKET_CONNECTED', socket });
}
function setCurrentUser(dispatch, response) {
localStorage.setItem('token', JSON.stringify(response.meta.token));
dispatch({ type: 'AUTHENTICATION_SUCCESS', response });
dispatch(fetchUserRooms(response.data.id));
connectToSocket(dispatch);
}
export function login(data, router) {
@@ -1,7 +1,57 @@
// @flow
import React from 'react';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { connectToChannel, leaveChannel } from '../../actions/room';
const Room = props =>
<div>Room {props.params.id}</div>;
type RoomType = {
id: number,
name: string,
}
export default Room;
type Props = {
socket: any,
channel: any,
room: RoomType,
params: {
id: number,
},
connectToChannel: () => void,
leaveChannel: () => void,
}
class Room extends Component {
componentDidMount() {
this.props.connectToChannel(this.props.socket, this.props.params.id);
}
componentWillReceiveProps(nextProps) {
if (nextProps.params.id !== this.props.params.id) {
this.props.leaveChannel(this.props.channel);
this.props.connectToChannel(nextProps.socket, nextProps.params.id);
}
if (!this.props.socket && nextProps.socket) {
this.props.connectToChannel(nextProps.socket, nextProps.params.id);
}
}
componentWillUnmount() {
this.props.leaveChannel(this.props.channel);
}
props: Props
render() {
return (
<div>{this.props.room.name}</div>
);
}
}
export default connect(
state => ({
room: state.room.currentRoom,
socket: state.session.socket,
channel: state.room.channel,
}),
{ connectToChannel, leaveChannel }
)(Room);
@@ -2,11 +2,13 @@ import { combineReducers } from 'redux';
import { reducer as form } from 'redux-form';
import session from './session';
import rooms from './rooms';
import room from './room';
const appReducer = combineReducers({
form,
session,
rooms,
room,
});
export default function (state, action) {
View
@@ -0,0 +1,19 @@
const initialState = {
channel: null,
currentRoom: {},
};
export default function (state = initialState, action) {
switch (action.type) {
case 'ROOM_CONNECTED_TO_CHANNEL':
return {
...state,
channel: action.channel,
currentRoom: action.response.room,
};
case 'USER_LEFT_ROOM':
return initialState;
default:
return state;
}
}
@@ -2,6 +2,7 @@ const initialState = {
isAuthenticated: false,
willAuthenticate: true,
currentUser: {},
socket: null,
};
export default function (state = initialState, action) {
@@ -29,6 +30,12 @@ export default function (state = initialState, action) {
willAuthenticate: false,
isAuthenticated: false,
currentUser: {},
socket: null,
};
case 'SOCKET_CONNECTED':
return {
...state,
socket: action.socket,
};
default:
return state;

0 comments on commit 1c1a394

Please sign in to comment.