Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chat feature #8

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@
/yarn-error.log
yarn-debug.log*
.yarn-integrity
dump.rdb
15 changes: 15 additions & 0 deletions app/channels/application_cable/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,20 @@

module ApplicationCable
class Connection < ActionCable::Connection::Base
# identified_by :current_user
# def connect
# self.current_user = find_verified_user
# end

# private
# def find_verified_user
# if
# verified_user = User.find_by(id: cookies.encrypted['session']['current_user_id'])

# verified_user
# else
# reject_unauthorized_connection
# end
# end
end
end
10 changes: 10 additions & 0 deletions app/channels/poll_channel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class PollChannel < ApplicationCable::Channel

def subscribed
stream_from "poll_#{params[:room]}"
end

def speak(data)
ActionCable.server.broadcast("poll_#{params[:room]}", { message: data["message"] })
end
end
6 changes: 5 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ def current_user
def current_session_expire
current_session = session[:expire_at]
current_time = Time.current
session.delete(:current_user_id) if current_session && current_session < current_time
if current_session && current_session < current_time
session.delete(:current_user_id)
else
current_session = 20.minutes.from_now
end
end
end
3 changes: 3 additions & 0 deletions app/controllers/chats_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class ChatsController < ApplicationController

end
30 changes: 30 additions & 0 deletions app/controllers/invites_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class InvitesController < ApplicationController
rescue_from ActiveRecord::RecordNotFound do
render :new
flash[:error] = 'User not found'
IgorChalenko marked this conversation as resolved.
Show resolved Hide resolved
end

def new
@poll = Poll.find(params[:poll_id])
authorize! @poll, to: :invite?
end

def create
@poll = Poll.find(params[:poll_id])
authorize! @poll, to: :invite?
@invited_user = User.where(username: params[:query]).or(User.where(email: params[:query])).take!
IgorChalenko marked this conversation as resolved.
Show resolved Hide resolved
if !invited?
@poll.members << @invited_user
redirect_to polls_path, success: "You invite #{@invited_user.username} to your poll"
else
render :new
flash[:error] = 'User alredy invited'
end
end

private
def invited?
@poll.memberships.where(user_id: @invited_user.id).exists?
end

end
68 changes: 68 additions & 0 deletions app/controllers/polls_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
class PollsController < ApplicationController

rescue_from ActiveRecord::RecordNotFound do
redirect_to polls_path, error: I18n.t('flash.poll.not_found')
end

def index
authorize! Poll, to: :index?
@active = current_user.polls.active
@upcoming = current_user.polls.upcoming
@ended = current_user.polls.ended
end

def show
@poll = Poll.find(params[:id])
authorize! @poll, to: :show?
@options = @poll.options
@members = @poll.members.count
end

def new
authorize! Poll, to: :create?
@poll = Poll.new
2.times { @poll.options.build }
end

def create
authorize! Poll, to: :create?
@poll = current_user.own_polls.new(poll_params)
if @poll.save
@poll.members << current_user
redirect_to polls_path, success: I18n.t('flash.poll.create')
else
render :new
end
end

def edit
@poll = Poll.find(params[:id])
@options = @poll.options
authorize! @poll, to: :update?
end

def update
@poll = Poll.find(params[:id])
authorize! @poll, to: :update?

if @poll.update(poll_params)
redirect_to poll_path(@poll.id), success: I18n.t('flash.poll.update')
else
render :new
end
end

def destroy
@poll = Poll.find(params[:id])
authorize! @poll, to: :destroy?
@poll.destroy
redirect_to polls_path, success: I18n.t('flash.poll.deleted')
end


private

def poll_params
params.require(:poll).permit(:title, :description, :start_date, :end_date, options_attributes: [:id, :vote_option, :_destroy])
end
end
14 changes: 14 additions & 0 deletions app/controllers/votes_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
class VotesController < ApplicationController

def update
@poll = current_user.polls.find(params[:poll_id])
@option = @poll.options.find(params[:id])
authorize! @poll, to: :vote?

if @poll.memberships.update(poll_option_id: @option.id)
redirect_to poll_path(@poll.id), success: 'Successfully voted'
else
render :show
end
end
end
3 changes: 3 additions & 0 deletions app/javascript/channels/consumer.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,7 @@

import { createConsumer } from "@rails/actioncable"

createConsumer('http://127.0.0.1:3000/cable');

export default createConsumer()

36 changes: 36 additions & 0 deletions app/javascript/channels/poll_channel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import consumer from "./consumer"



const poll = document.querySelector('#poll');
const poll_id = poll.dataset.pollId;

const pollChannel = consumer.subscriptions.create({channel: "PollChannel", room: `poll_${poll_id}`}, {
connected() {
// Called when the subscription is ready for use on the server

console.log("Connected to the poll room!");
},
received(data) {
let mess = document.createElement('span'),
col = document.createElement('div');
col.classList.add('col', 'mb-4')
mess.classList.add('bg-info', 'rounded-pill', 'text-white', 'p-2');
mess.innerHTML = data.message;
col.insertAdjacentElement('afterbegin', mess);
document.querySelector('#chat_holder').insertAdjacentElement('beforeend', col)

console.log(data);
},

disconnected() {
// Called when the subscription has been terminated by the server
},
speak(message) {
this.perform('speak', { message: message })
}
});


export default pollChannel;

30 changes: 30 additions & 0 deletions app/javascript/packs/add_button_script/script.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
document.addEventListener('DOMContentLoaded', () => {

const addButton = document.querySelector('#btn'),
options = document.querySelector('#options');


let counter = 2;

addButton.addEventListener('click', (e) => {
const optionArray = document.querySelectorAll('[name*="[vote_option]"]');
e.preventDefault();
if (optionArray.length < 5) {
const col = document.createElement('div');
const input = `<input id="poll_options_attributes_${counter}_vote_option" class="form-control" placeholder="Option"
type="text" name="poll[options_attributes][${counter}][vote_option]"></input>
<input id="poll_options_attributes_${counter}__destroy" type="checkbox" value="1"
name="poll[options_attributes][${counter}][_destroy]"></input>`;

col.classList.add('col-4');
col.innerHTML = input;
options.insertAdjacentElement('beforeend', col);

counter++;
} else {
addButton.style.display = 'none';

}
});

});
22 changes: 21 additions & 1 deletion app/javascript/packs/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,27 @@ import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import pollChannel from "../channels/poll_channel"

Rails.start()
Turbolinks.start()
// Turbolinks.start()
ActiveStorage.start()

require('./add_button_script/script');

document.addEventListener('DOMContentLoaded', () => {
const btn = document.querySelector('#button-addon2'),
input = document.querySelector('#message_input');

btn.addEventListener('click', (e) => {
e.preventDefault();
let message = input.value;
if (message.length > 0) {
pollChannel.speak(message);
input.value = "";
}
});
})



28 changes: 28 additions & 0 deletions app/models/poll.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Poll < ApplicationRecord
belongs_to :user
has_many :memberships, class_name: 'PollMembership', foreign_key: "poll_id"
has_many :members, -> { distinct }, through: :memberships, source: :user, dependent: :destroy
has_many :options, class_name: "PollOption", dependent: :destroy

accepts_nested_attributes_for :options, allow_destroy: true, reject_if: lambda {|attributes| attributes['vote_option'].blank?}


validates :title, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
validate :end_date_before_start_date

scope :upcoming, -> { where('start_date > ?', Date.today) }
scope :active, -> { where('start_date <= ? and end_date >= ?', Date.today, Date.today) }
scope :ended, -> { where('end_date < ?', Date.today) }

private
def end_date_before_start_date
return if start_date.blank? || end_date.blank?

errors.add(:end_date, "can't be earlier than start date ") if end_date <= start_date
end



end
5 changes: 5 additions & 0 deletions app/models/poll_membership.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class PollMembership < ApplicationRecord
belongs_to :user
belongs_to :poll
belongs_to :poll_option, dependent: :destroy, optional: true
end
6 changes: 6 additions & 0 deletions app/models/poll_option.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class PollOption < ApplicationRecord
has_many :memberships, dependent: :nullify, class_name: 'PollMembership'

belongs_to :poll, counter_cache: :vote_count

end
4 changes: 4 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ class User < ApplicationRecord
VALID_EMAIL_REGEX = /\A[\w+]+@[a-z\d]+\.[a-z]+\z/i

has_secure_password
has_many :own_polls, dependent: :destroy, class_name: 'Poll'
has_many :memberships, class_name: 'PollMembership', foreign_key: "user_id"
has_many :polls,-> { distinct }, through: :memberships, source: :poll


validates :username, presence: true
validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: true
Expand Down
1 change: 1 addition & 0 deletions app/policies/application_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ class ApplicationPolicy < ActionPolicy::Base
def logged_in?
user.present?
end

end
42 changes: 42 additions & 0 deletions app/policies/poll_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# frozen_string_literal: true

class PollPolicy < ApplicationPolicy
def index?
logged_in?
end

def create?
logged_in?
end

def show?
invited?
end

def update?
owner?
end

def destroy?
owner?
end

def invite?
owner?
end
def vote?
invited? && !voted?
end

def owner?
user.id == record.user_id
end

def invited?
record.memberships.where(user_id: user.id).exists?
end

def voted?
record.memberships.where(user_id: user.id).and(record.memberships.where.not(poll_option_id: nil)).exists?
end
end
1 change: 0 additions & 1 deletion app/policies/user_policy.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true

class UserPolicy < ApplicationPolicy

def create?
!logged_in?
end
Expand Down
8 changes: 8 additions & 0 deletions app/views/invites/_invite_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<%= form_tag poll_invites_path, method: :post, class: "border rounded p-4 mt-5" do %>
<div class="mb-3">
<%= text_field_tag :query, params[:query], class: "form-control" %>
</div>
<div class="col-3">
<%= submit_tag "Send invite", class: "btn btn-primary" %>
</div>
<% end %>
Loading