diff --git a/.gitignore b/.gitignore index e941da68..90286207 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +.DS_Store .bundle vendor/bundle -.DS_Store +*.swp +*.swo diff --git a/.rspec b/.rspec deleted file mode 100644 index 660778bd..00000000 --- a/.rspec +++ /dev/null @@ -1 +0,0 @@ ---colour --format documentation diff --git a/Gemfile b/Gemfile index 1f478929..53fdd760 100644 --- a/Gemfile +++ b/Gemfile @@ -1,5 +1,9 @@ source 'https://rubygems.org' ruby '2.0.0' -gem 'rspec', '~> 2.14.1' +gem 'sinatra' +gem 'sinatra-contrib' gem 'pry-byebug' +gem 'thin' +gem 'pg' +gem 'rake' diff --git a/Gemfile.lock b/Gemfile.lock index a885a8c7..93d8cc90 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,15 +1,19 @@ GEM remote: https://rubygems.org/ specs: + backports (3.6.4) byebug (3.5.1) columnize (~> 0.8) debugger-linecache (~> 1.2) slop (~> 3.6) coderay (1.1.0) - columnize (0.8.9) + columnize (0.9.0) + daemons (1.1.9) debugger-linecache (1.2.0) - diff-lcs (1.2.5) + eventmachine (1.0.3) method_source (0.8.2) + multi_json (1.10.1) + pg (0.17.1) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -17,19 +21,37 @@ GEM pry-byebug (2.0.0) byebug (~> 3.4) pry (~> 0.10) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.7) - rspec-expectations (2.14.5) - diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.5) + rack (1.5.2) + rack-protection (1.5.3) + rack + rack-test (0.6.2) + rack (>= 1.0) + rake (10.3.2) + sinatra (1.4.5) + rack (~> 1.4) + rack-protection (~> 1.4) + tilt (~> 1.3, >= 1.3.4) + sinatra-contrib (1.4.2) + backports (>= 2.0) + multi_json + rack-protection + rack-test + sinatra (~> 1.4.0) + tilt (~> 1.3) slop (3.6.0) + thin (1.6.3) + daemons (~> 1.0, >= 1.0.9) + eventmachine (~> 1.0) + rack (~> 1.0) + tilt (1.4.1) PLATFORMS ruby DEPENDENCIES + pg pry-byebug - rspec (~> 2.14.1) + rake + sinatra + sinatra-contrib + thin diff --git a/README.md b/README.md new file mode 100644 index 00000000..8e5038ac --- /dev/null +++ b/README.md @@ -0,0 +1,14 @@ +# Chatitude + +A start on a chat server built using Sinatra and server sent events. The SSE necessitated the use of an evented web server so WEBrick was swapped out for Thin. + +## Notes + +The *client* closes connections after around 40 seconds and the only remedy I could find is to require the client to send a PING message periodically and have the server respond with a PONG message. The server response is what will actually keep the connection open. + +Chat users are identified by their ID which is stored in the session. Users have to sign up and have an account (username & password) to begin chatting. + +This line in the `get '/chat'` endpoint adds a method called `user` to the instance of Sinatra::Helpers::Stream so that they can be identified for keeping connections open and later for private messages. +``` +def out.user; @current_user; end +``` diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..f82d6eb1 --- /dev/null +++ b/Rakefile @@ -0,0 +1,45 @@ +task :environment do + require './lib/chatitude' +end + +task :console => :environment do + require 'irb' + ARGV.clear + IRB.start +end + +namespace :db do + task :create do + `createdb chatitude` + puts 'Database \'chatitude\' created!' + end + + task :drop do + `dropdb chatitude` + puts 'Database \'chatitude\' dropped!' + end + + task :create_tables => :environment do + db = Chatitude.create_db_connection 'chatitude' + Chatitude.create_tables db + puts 'Created tables.' + end + + task :drop_tables => :environment do + db = Chatitude.create_db_connection 'chatitude' + Chatitude.drop_tables db + puts 'Dropped tables.' + end + + task :clear => :environment do + db = Chatitude.create_db_connection 'chatitude' + Chatitude.clear_tables db + puts 'Cleared tables.' + end + + task :seed => :environment do + db = Chatitude.create_db_connection 'chatitude' + Chatitude.seed_dummy_users db + puts 'Tables seeded.' + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 00000000..ad41ffe7 --- /dev/null +++ b/config.ru @@ -0,0 +1,3 @@ +require './server' + +run Chatitude::Server diff --git a/lib/chatitude.rb b/lib/chatitude.rb new file mode 100644 index 00000000..7b5779d2 --- /dev/null +++ b/lib/chatitude.rb @@ -0,0 +1,44 @@ +require 'pg' +require_relative 'chatitude/repos/users_repo.rb' + +module Chatitude + def self.create_db_connection dbname + PG.connect(host: 'localhost', dbname: dbname) + end + + def self.clear db + db.exec <<-SQL + DELETE FROM users; + DELETE FROM sessions; + SQL + end + + def self.create_tables db + db.exec <<-SQL + CREATE TABLE IF NOT EXISTS users( + id SERIAL PRIMARY KEY, + username VARCHAR, + password VARCHAR + ); + CREATE TABLE IF NOT EXISTS sessions( + id SERIAL PRIMARY KEY, + user_id INTEGER REFERENCES users (id), + token TEXT UNIQUE + ); + SQL + end + + def self.drop_tables db + db.exec <<-SQL + DROP TABLE IF EXISTS users; + DROP TABLE IF EXISTS sessions; + SQL + end + + def self.seed_dummy_users db + db.exec <<-SQL + INSERT INTO users (username, password) + VALUES ('nick','nick'), ('kate','kate'); + SQL + end +end diff --git a/lib/chatitude/repos/users_repo.rb b/lib/chatitude/repos/users_repo.rb new file mode 100644 index 00000000..8102ca0e --- /dev/null +++ b/lib/chatitude/repos/users_repo.rb @@ -0,0 +1,49 @@ +require 'securerandom' + +module Chatitude + class UsersRepo + def self.find db, user_id + sql = %q[SELECT * FROM users WHERE id = $1] + result = db.exec(sql, [user_id]) + result.first + end + + def self.find_by_name db, username + sql = %q[SELECT * FROM users WHERE username = $1] + result = db.exec(sql, [username]) + result.first + end + + def self.find_by_token db, token + sql = %q[ + SELECT + u.id + , u.username + , u.password + FROM sessions s + JOIN users u + ON s.user_id = u.id + WHERE s.token = $1 + ] + result = db.exec(sql, [token]) + result.first + end + + def self.save db, user_data + sql = %q[INSERT INTO users (username, password) VALUES ($1, $2) RETURNING *] + result = db.exec(sql, [user_data[:username], user_data[:password]]) + result.first + end + + def self.sign_in db, id + token = SecureRandom.hex(16) + sql = %q[INSERT INTO sessions (user_id, token) VALUES ($1, $2)] + result = db.exec(sql, [id, token]) + token + end + + def self.sign_out db, token + db.exec("DELETE FROM sessions WHERE token = $1", [token]) + end + end +end diff --git a/public/index.html b/public/index.html new file mode 100644 index 00000000..ab55abe5 --- /dev/null +++ b/public/index.html @@ -0,0 +1 @@ +