diff --git a/Gemfile b/Gemfile index b1a320395a..fdac52c66f 100644 --- a/Gemfile +++ b/Gemfile @@ -11,3 +11,12 @@ end group :development, :test do gem 'rubocop', '1.20' end + +gem "sinatra", "~> 3.0" +gem "sinatra-contrib", "~> 3.0" +gem "webrick", "~> 1.8" +gem "rack-test", "~> 2.1" + +gem "pg", "~> 1.4" + +gem "bcrypt" diff --git a/Gemfile.lock b/Gemfile.lock index 66064703c7..f0a10ac592 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,11 +3,21 @@ GEM specs: ansi (1.5.0) ast (2.4.2) + bcrypt (3.1.18) diff-lcs (1.4.4) docile (1.4.0) + multi_json (1.15.0) + mustermann (3.0.0) + ruby2_keywords (~> 0.0.1) parallel (1.20.1) parser (3.0.2.0) ast (~> 2.4.1) + pg (1.4.6) + rack (2.2.6.4) + rack-protection (3.0.6) + rack + rack-test (2.1.0) + rack (>= 1.3) rainbow (3.0.0) regexp_parser (2.1.1) rexml (3.2.5) @@ -36,6 +46,7 @@ GEM rubocop-ast (1.11.0) parser (>= 3.0.1.1) ruby-progressbar (1.11.0) + ruby2_keywords (0.0.5) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) @@ -46,18 +57,37 @@ GEM terminal-table simplecov-html (0.12.3) simplecov_json_formatter (0.1.3) + sinatra (3.0.6) + mustermann (~> 3.0) + rack (~> 2.2, >= 2.2.4) + rack-protection (= 3.0.6) + tilt (~> 2.0) + sinatra-contrib (3.0.6) + multi_json + mustermann (~> 3.0) + rack-protection (= 3.0.6) + sinatra (= 3.0.6) + tilt (~> 2.0) terminal-table (3.0.1) unicode-display_width (>= 1.1.1, < 3) + tilt (2.1.0) unicode-display_width (2.0.0) + webrick (1.8.1) PLATFORMS ruby DEPENDENCIES + bcrypt + pg (~> 1.4) + rack-test (~> 2.1) rspec rubocop (= 1.20) simplecov simplecov-console + sinatra (~> 3.0) + sinatra-contrib (~> 3.0) + webrick (~> 1.8) RUBY VERSION ruby 3.0.2p107 diff --git a/app.rb b/app.rb new file mode 100644 index 0000000000..26f603e97d --- /dev/null +++ b/app.rb @@ -0,0 +1,100 @@ +require 'sinatra/base' +require 'sinatra/reloader' +require_relative 'lib/database_connection' +require_relative 'lib/user_repository' +require_relative 'lib/peep_repository' +require 'bcrypt' + +DatabaseConnection.connect + +class Application < Sinatra::Base + enable :sessions + # This allows the app code to refresh + # without having to restart the server. + configure :development do + register Sinatra::Reloader + also_reload 'lib/user_repository' + also_reload 'lib/peep_repository' + end + + get '/' do + user_repo = UserRepository.new + peep_repo = PeepRepository.new + peeps = peep_repo.all + + @complete_peep_info = [] + + peeps.each do |peep| + user = user_repo.find(peep.user_id) + peep_info = {peep: peep, name: user.name, username: user.username} + @complete_peep_info << peep_info + end + + return erb(:index) # add log in status logic + end + + get '/login' do + return erb(:login) + end + + post '/login' do + email = params[:email] + password = params[:password] + + user_repo = UserRepository.new + + user = user_repo.find_by_email(email) + stored_password = BCrypt::Password.new(user.password) + + if stored_password == password + session[:user_id] = user.id + return erb(:login_success) + else + return erb(:wrong_password) + end + end + + get '/peeps/new' do + if session[:user_id] == nil + return erb(:login) + else + return erb(:new_peep) + end + end + + post '/peeps' do + if session[:user_id] == nil + return erb(:login) + else + peep_repo = PeepRepository.new + new_peep = Peep.new + new_peep.time = Time.now + new_peep.content = params[:content] + new_peep.user_id = session[:user_id] + + peep_repo.create(new_peep) + + return erb(:new_peep_success) + end + end + + get '/signup' do + return erb(:signup) + end + + post '/signup' do + repo = UserRepository.new + new_user = User.new + + new_user.name = params[:name] # name and username must be unique + new_user.username = params[:username] + new_user.email = params[:email] + new_user.password = params[:password] + + repo.create(new_user) + + return erb(:signup_success) + end + + # add invalid_parameters? method +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..c41dba5056 --- /dev/null +++ b/config.ru @@ -0,0 +1,3 @@ +# file: config.ru +require './app' +run Application diff --git a/lib/database_connection.rb b/lib/database_connection.rb new file mode 100644 index 0000000000..f7cc49dd3e --- /dev/null +++ b/lib/database_connection.rb @@ -0,0 +1,38 @@ +# file: lib/database_connection.rb + +require 'pg' + +# This class is a thin "wrapper" around the +# PG library. We'll use it in our project to interact +# with the database using SQL. + +class DatabaseConnection + # This method connects to PostgreSQL using the + # PG gem. We connect to 127.0.0.1, and select + # the database name given in argument. + def self.connect + if ENV['DATABASE_URL'] != nil + @connection = PG.connect(ENV['DATABASE_URL']) + return + end + + if ENV['ENV'] == 'test' + database_name = 'chitter_challenge_test' + else + database_name = 'chitter_challenge' + end + @connection = PG.connect({ host: '127.0.0.1', dbname: database_name }) + end + + # This method executes an SQL query + # on the database, providing some optional parameters + # (you will learn a bit later about when to provide these parameters). + def self.exec_params(query, params) + if @connection.nil? + raise 'DatabaseConnection.exec_params: Cannot run a SQL query as the connection to'\ + 'the database was never opened. Did you make sure to call first the method '\ + '`DatabaseConnection.connect` in your app.rb file (or in your tests spec_helper.rb)?' + end + @connection.exec_params(query, params) + end +end diff --git a/lib/peep.rb b/lib/peep.rb new file mode 100644 index 0000000000..fb08566584 --- /dev/null +++ b/lib/peep.rb @@ -0,0 +1,3 @@ +class Peep + attr_accessor :id, :time, :content, :user_id +end diff --git a/lib/peep_repository.rb b/lib/peep_repository.rb new file mode 100644 index 0000000000..9a538d250f --- /dev/null +++ b/lib/peep_repository.rb @@ -0,0 +1,30 @@ +require_relative 'peep' + +class PeepRepository + def all + peeps = [] + + sql = 'SELECT id, time, content, user_id FROM peeps ORDER BY time DESC;' + result_set = DatabaseConnection.exec_params(sql, []) + + result_set.each do |record| + peep = Peep.new + peep.id = record['id'] + peep.time = record['time'] + peep.content = record['content'] + peep.user_id = record['user_id'] + + peeps << peep + end + + return peeps + end + + def create(peep) + sql = 'INSERT INTO peeps (time, content, user_id) VALUES ($1, $2, $3);' + params = [peep.time, peep.content, peep.user_id] + result_set = DatabaseConnection.exec_params(sql, params) + + return peep + end +end diff --git a/lib/user.rb b/lib/user.rb new file mode 100644 index 0000000000..9b8f2a11fb --- /dev/null +++ b/lib/user.rb @@ -0,0 +1,3 @@ +class User + attr_accessor :id, :name, :username, :email, :password +end diff --git a/lib/user_repository.rb b/lib/user_repository.rb new file mode 100644 index 0000000000..bf291fe712 --- /dev/null +++ b/lib/user_repository.rb @@ -0,0 +1,42 @@ +require_relative 'user' +require 'bcrypt' + +class UserRepository + def create(new_user) + encrypted_password = BCrypt::Password.create(new_user.password) + + sql = 'INSERT INTO users (name, username, email, password) VALUES ($1, $2, $3, $4);' + params = [new_user.name, new_user.username, new_user.email, encrypted_password] + DatabaseConnection.exec_params(sql, params) + + return new_user + end + + def find_by_email(email) + sql = 'SELECT id, name, username, email, password FROM users WHERE email = $1;' + result_set = DatabaseConnection.exec_params(sql, [email]) + + user = User.new + user.id = result_set[0]['id'] + user.name = result_set[0]['name'] + user.username = result_set[0]['username'] + user.email = result_set[0]['email'] + user.password = result_set[0]['password'] + + return user + end + + def find(id) + sql = 'SELECT id, name, username, email, password FROM users WHERE id = $1;' + result_set = DatabaseConnection.exec_params(sql, [id]) + + user = User.new + user.id = result_set[0]['id'] + user.name = result_set[0]['name'] + user.username = result_set[0]['username'] + user.email = result_set[0]['email'] + user.password = result_set[0]['password'] + + return user + end +end diff --git a/route_plan.md b/route_plan.md new file mode 100644 index 0000000000..6c642a0c78 --- /dev/null +++ b/route_plan.md @@ -0,0 +1,161 @@ +# {{ METHOD }} {{ PATH}} Route Design Recipe + +_Copy this design recipe template to test-drive a Sinatra route._ + +## 1. Design the Route Signature + +[x] Page: homepage +Request: GET / (peep_repo.all) +No parameters +Response (200 OK) +index view with list of peeps, login status, link to log in/out, links to sign up and create peep + +[x] Page: create new peep +Request: GET /peeps/new +No parameters +Response (200 OK) +new_peep view with form to create new peep + +[x] Page: new peep created (peep_repo.create(new_peep)) +Request: POST /peeps +Parameters: name, username, time, content +Response (200 OK) +new_peep_success view with link to homepage + +[x] Page: signup +Request: GET /signup +No parameters +Response (200 OK) +signup view with form to create a new user + +[x] Page: signup successful (user_repo.create(new_user)) +Request: POST /signup +Parameters: name, email, username, password +Response (200 OK) +signup success view with link to login + +[x] Page: log in +Request: GET /login +No parameters +Response (200 OK) +login view with form to log in + +[x] Page: log in successful (user_repo.find_by_email(email)) +Request: POST /login +Parameters: email, password +Response (200 OK) +login success view with link to homepage + +[ ] Page: log out +Request: GET /logout +No parameters +Response (200 OK) +logout view with button to log out + +[ ] Page: log out successful +Request: POST /logout +Parameters: user_id? +Response (200 OK) +Redirect to homepage + +You'll need to include: + * the HTTP method + * the path + * any query parameters (passed in the URL) + * or body parameters (passed in the request body) + +## 2. Design the Response + +The route might return different responses, depending on the result. + +For example, a route for a specific blog post (by its ID) might return `200 OK` if the post exists, but `404 Not Found` if the post is not found in the database. + +Your response might return plain text, JSON, or HTML code. + +_Replace the below with your own design. Think of all the different possible responses your route will return._ + +```html + + + + +
+ ++ +
+ + diff --git a/views/new_peep.erb b/views/new_peep.erb new file mode 100644 index 0000000000..a620f3fb48 --- /dev/null +++ b/views/new_peep.erb @@ -0,0 +1,16 @@ + + + + ++ New Peep created! + Go back to the homepage. +
+ + diff --git a/views/signup.erb b/views/signup.erb new file mode 100644 index 0000000000..6f369b96af --- /dev/null +++ b/views/signup.erb @@ -0,0 +1,22 @@ + + + + ++ Sign up successful! + Go back to the homepage. +
+ + diff --git a/views/wrong_password.erb b/views/wrong_password.erb new file mode 100644 index 0000000000..1a74cb2e0f --- /dev/null +++ b/views/wrong_password.erb @@ -0,0 +1,13 @@ + + + + +