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

Evan Thomas Chitter Challenge #2187

Open
wants to merge 56 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3cf4eaa
Set up rspec folder structure and sinatra config
ev-th May 5, 2023
686a27c
Write sql seeds files
ev-th May 5, 2023
7ae5ca5
Set up empty database files
ev-th May 5, 2023
e04cb8d
Write UserRepository#find
ev-th May 5, 2023
b41415d
Write UserRepository#all and UserRepository#create
ev-th May 5, 2023
2a3a279
Write PeepRepository#all
ev-th May 5, 2023
880717b
Write PeepRepository#find and PeepRepository#create
ev-th May 5, 2023
29888d6
Rewrite DatabaseConnection to allow for deployment with repote database
ev-th May 5, 2023
38ba99b
Set up app.rb and move reset tables methods into spec_helper
ev-th May 5, 2023
d00069c
Write the skeleton for http route tests
ev-th May 5, 2023
e987bf8
Fix bug with getting timestamp from the database
ev-th May 5, 2023
469293e
Write GET / route and initial homepage html
ev-th May 5, 2023
e3ae33e
Write GET /sign-up route and sign up page html
ev-th May 5, 2023
ae84c4d
Write GET /new-peep route and html form:
ev-th May 5, 2023
3065380
Write POST /sign-up route with success and html
ev-th May 5, 2023
7bb239c
Write POST /new-peep route for valid params and html success page
ev-th May 5, 2023
c97f8a3
Remove time added from new peep form due to dynamic generation
ev-th May 5, 2023
e022178
Update GET / to return peeps in reverse chronological order
ev-th May 5, 2023
10c1494
Fix rubocop offenses
ev-th May 5, 2023
d38fe32
sanitise new-user input to check for empty strings
ev-th May 6, 2023
b651d7e
Sanitise user input for invalid characters
ev-th May 6, 2023
6bc43ca
Validate input for POST /new-peep
ev-th May 6, 2023
a8cbb33
Write UserRepository#username_exists? and #email_exists
ev-th May 9, 2023
ddbc07a
Update POST /sign-up to only allow new usernames and emails to sign up
ev-th May 9, 2023
dd44160
Use bcrypt to encyrpt passwords
ev-th May 9, 2023
16ff5c3
Make GET /login and html page
ev-th May 9, 2023
b3bf420
Write UserRepository#find_by_email
ev-th May 9, 2023
d6ae431
Start writing the tests and implementation for logging in the user
ev-th May 9, 2023
547554e
Write UserRepository#correct_password
ev-th May 9, 2023
9938503
Implement user login
ev-th May 9, 2023
c93e935
UserRepository#find_by_email returns nil if not in db
ev-th May 9, 2023
a51ce00
Create login failure page and do not login with wrong password
ev-th May 9, 2023
c872fc8
Only show login and sign up links when not logged in
ev-th May 9, 2023
ce770e9
implement logout
ev-th May 10, 2023
71088c1
Use login session to assign new peeps to the logged in user
ev-th May 10, 2023
02364fc
Hompage displays name with peeps
ev-th May 10, 2023
2797a84
Add .env to .gitignore
ev-th May 10, 2023
67d5a0c
Set up dotenv and EmailSender class
ev-th May 10, 2023
162d3c8
Implement EmailSender
ev-th May 10, 2023
937354e
Begin writing EmailSender test
ev-th May 10, 2023
ace0311
Write UserRepository#find_by_username
ev-th May 10, 2023
93cd1d9
Email user when tagged in peep
ev-th May 10, 2023
ea318d8
Add some css styling
ev-th May 10, 2023
7d502dd
Update a test to pass with changed html tag
ev-th May 10, 2023
5bf835f
Update peep display with new background image
ev-th May 10, 2023
a071798
Change the bcrypt hashing cost
ev-th May 11, 2023
12328a8
Setup for deploy
ev-th May 11, 2023
19170b8
Remove quotes from css url functions
ev-th May 11, 2023
48846b1
Write Peep#formatted_time
ev-th May 11, 2023
de8f0be
Update styling to make small font more readable. Fix html text on new…
ev-th May 11, 2023
1ab159d
Change date formatting of peep date and time on the homepage
ev-th May 11, 2023
1e255ea
Restyle the peeps: Translucent background resized fonts, reordered info
ev-th May 11, 2023
1e43f0f
Refactor EmailSender
ev-th May 11, 2023
adbc99f
Refactor for style guide
ev-th May 11, 2023
4d20e9f
Extract logging in a user in tests to a method
ev-th May 11, 2023
ce69d21
Write README.md
ev-th May 11, 2023
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@

# Local cache of Rubocop remote config
.rubocop-*

.env
15 changes: 15 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,18 @@ 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.5"

gem "bcrypt", "~> 3.1"

gem "dotenv", "~> 2.8"

gem "sib-api-v3-sdk", "~> 9.1"

gem "rackup", "~> 1.0"
51 changes: 51 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,13 +1,34 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ansi (1.5.0)
ast (2.4.2)
bcrypt (3.1.18)
diff-lcs (1.4.4)
docile (1.4.0)
dotenv (2.8.1)
ethon (0.16.0)
ffi (>= 1.15.0)
ffi (1.15.5)
json (2.6.3)
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.5.3)
public_suffix (5.0.1)
rack (2.2.7)
rack-protection (3.0.6)
rack
rack-test (2.1.0)
rack (>= 1.3)
rackup (1.0.0)
rack (< 3)
webrick
rainbow (3.0.0)
regexp_parser (2.1.1)
rexml (3.2.5)
Expand Down Expand Up @@ -36,6 +57,11 @@ GEM
rubocop-ast (1.11.0)
parser (>= 3.0.1.1)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
sib-api-v3-sdk (9.1.0)
addressable (~> 2.3, >= 2.3.0)
json (~> 2.1, >= 2.1.0)
typhoeus (~> 1.0, >= 1.0.1)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
Expand All @@ -46,18 +72,43 @@ 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)
typhoeus (1.4.0)
ethon (>= 0.9.0)
unicode-display_width (2.0.0)
webrick (1.8.1)

PLATFORMS
ruby
x86_64-linux

DEPENDENCIES
bcrypt (~> 3.1)
dotenv (~> 2.8)
pg (~> 1.5)
rack-test (~> 2.1)
rackup (~> 1.0)
rspec
rubocop (= 1.20)
sib-api-v3-sdk (~> 9.1)
simplecov
simplecov-console
sinatra (~> 3.0)
sinatra-contrib (~> 3.0)
webrick (~> 1.8)

RUBY VERSION
ruby 3.0.2p107
Expand Down
79 changes: 79 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,85 @@
Chitter Challenge
=================

An implementation of the solo project from the Makers Academy Web Applications module. The deployed app can be found at https://chitter-challenge-ct9w.onrender.com/

Running the app locally
-----------------------
```
$ git clone https://github.com/ev-th/chitter-challenge.git
$ bundle install
$ createdb chitter
$ psql -h 127.0.0.1 chitter < chitter.sql
$ createdb chitter_test
$ psql -h 127.0.0.1 chitter_test < chitter.sql
$ rspec
$ rackup
```

App features
------------
* A user can sign up for an account. Username and email address must be unique.
* Users can log in and out using their account.
* Users can post peeps when logged in.
* Peeps are listed on the homepage in reverse chronological order.
* Peeps are displayed with the user's name and username and a timestamp.
* When a user is tagged in a peep, they will receive a notification email.

Technologies
------------
* Languages: Ruby, HTML, CSS, SQL
* git and GitHub
* Sinatra
* PostgreSQL
* Bcrypt
* Brevo API
* Render
* Excalidraw
* rack
* RSpec
* rubocop

Approaching the challenge
-------------------------
This was a big challenge with lots of optional extras, so I wasn't sure what I'd be able to achieve in the time. Therefore, I took the approach to create an MVP with the 'straight up' features first, then iteratively add additional features as time allowed.

To start, I planned the views, then the necessary routes:
![excalidraw_design](./excalidraw_design.png)

Then I planned the database tables. The database has two tables: peeps and users. Users has a one to many relationship with peeps. I set this up and wrote the seeds.

After that, I wrote the database repositories. The fastest option for me was to write the repositories myself as I'm currently unfamiliar with ORMs. This was something I'd hoped to revisit at the end.

Then I wrote the https routes and sanitised user input for empty inputs and any angle brackets that could be an attempt to inject html by a user. This is a pretty basic attempt as sanitising user input and is another aspect of the project that could be examined further.

In order to finish the 'straight up' version of the challenge, I added functionality to the database repository to check whether a username or email already exists in the users table to ensure there are no duplications when a user signs up. If users sign up with a duplicated email or username, or input invalid data, they are taken to a failure page with a 400 status code. I also used the bcrypt gem to encrypt user passwords.

Once the basic features were complete, I moved to the advanced ones. I implemented log in and log out using sessions built into Sinatra. With this, I updated the homepage to adapt to the session. If the user was logged in they could make a new peep or log out. If the user was not logged in they could sign up or log in. Now that the user can log in, I updated the new peep POST route to use session data for user id instead of having the user input their id manually.

Next, I implemented the email sending feature using Brevo. I copied some boilerplate code from their documentation for this and attempted to unit test it, but it was proving to be very complex and didn't seem to be a good use of time. Therefore this is not covered in the test suite. However when testing it manually, it was working. I put secret data in a .env file and added that to my .gitignore. I also added these to my Render environment variables and checked that the emails were also being sent in the deployed version.

Finally, I added some CSS styling. Currently there is one style sheet used for all the views. I had some fun here! 🐦

What would I do with more time?
-------------------------------
Overall I'm happy with what I achieved, but there are a number of things I would like to have done if I had more time:

* I missed the final bonus feature to add replies to peeps.
* Following on from the previous point, I would have liked the app to have pages for users, where you can see only their peeps, along with comments on the peeps. This is where the one to many relationship between the database tables would have made sense.
* The failure pages for invalid user inputs are very basic and don't give the user information about why the input was invalid. I would have liked more detail here.
* Bcrypt was severely slowing my tests down so I turned down the cost slightly to make it more workable while in development. I would like to work out how to only turn down costs in tests and leave it higher for deployed code.
* HTML form input sanitisation was very basic and could have been developed further.
* I would implement ActiveRecord as an ORM.
* Overall, I'm happy with my testing and used TDD throughout the majority of the process. However, I struggled to use testing when implementing the email sender, so this is not covered by the test suite.
* I'd like to have spent some more time on styling.

---

The following are the specifications for the project provided by Makers:

Chitter Challenge Specifications
================================

* Feel free to use Google, your notes, books, etc. but work on your own
* If you refer to the solution of another coach or student, please put a link to that in your README
* If you have a partial solution, **still check in a partial solution**
Expand Down
152 changes: 152 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
require 'bcrypt'
require 'sinatra/base'
require 'sinatra/reloader'
require_relative 'lib/peep_repository'
require_relative 'lib/user_repository'
require_relative 'lib/database_connection'
require_relative 'lib/email_sender'

DatabaseConnection.connect

class Application < Sinatra::Base
enable :sessions

configure :development do
register Sinatra::Reloader
also_reload 'lib/user_repository'
also_reload 'lib/peep_repository'
end

get '/' do
peep_repo = PeepRepository.new
peeps = peep_repo.all
@peeps = peeps.sort_by(&:time_posted).reverse

user_repo = UserRepository.new
@peeps.each { |peep| peep.user = user_repo.find(peep.user_id) }
@user = session[:user_id].nil? ? nil : user_repo.find(session[:user_id])

erb(:index)
end

get '/login' do
erb(:login)
end

get '/sign-up' do
erb(:sign_up)
end

get '/new-peep' do
return redirect('/login') if session[:user_id].nil?
erb(:new_peep)
end

get '/logout' do
session[:user_id] = nil
return redirect('/')
end

post '/login' do
repo = UserRepository.new
email = params[:email]
password = params[:password]

unless input_valid?(email) && input_valid?(password) && repo.email_exists?(email)
status 400
return erb(:login_failure)
end

unless repo.correct_password?(email, password)
status 400
return erb(:login_failure)
end

@user = repo.find_by_email(email)
session[:user_id] = @user.id
return erb(:login_success)
end

post '/sign-up' do
unless params.values.all? { |input| input_valid?(input) }
status 400
return erb(:sign_up_failure)
end

repo = UserRepository.new

if repo.email_exists?(params[:email]) || repo.username_exists?(params[:username])
status 400
return erb(:sign_up_failure)
end

user = get_user_from_params(params)
repo.create(user)
return erb(:sign_up_success)
end

post '/new-peep' do
unless params.values.all? { |input| input_valid?(input) }
status 400
return erb(:new_peep_failure)
end

repo = PeepRepository.new
peep = get_peep_from_params(params)
repo.create(peep)

email_tagged_users(peep)
erb(:new_peep_success)
end

private

def get_user_from_params(params)
user = User.new
user.email = params[:email]
user.password = params[:password]
user.name = params[:name]
user.username = params[:username]
user
end

def get_peep_from_params(params)
peep = Peep.new
peep.content = params[:content]
peep.time_posted = params[:time_posted] || Time.new
peep.user_id = session[:user_id]
peep
end

def email_tagged_users(peep)
users = get_tagged_users(peep)
return if users.nil?

users.each do |user|
email_sender = EmailSender.new(
user.email,
'<html>You have been tagged in a peep!</html>'
)
email_sender.send_email
end
end

def get_tagged_users(peep)
words = peep.content.split
tags = words.select { |word| word.start_with?("@") }
usernames = tags.map { |tag| tag[1..] }

repo = UserRepository.new

users = []
usernames.each do |username|
user = repo.find_by_username(username)
users << user unless user.nil?
end
users
end

def input_valid?(input)
input != '' && !input.match(/[<>]/)
end
end
19 changes: 19 additions & 0 deletions chitter.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
DROP TABLE IF EXISTS users, peeps;

CREATE TABLE users (
id SERIAL PRIMARY KEY,
email text,
password text,
name text,
username text
);

CREATE TABLE peeps (
id SERIAL PRIMARY KEY,
content text,
time_posted timestamp,
user_id integer,
constraint fk_user foreign key(user_id)
references users(id)
on delete cascade
);
2 changes: 2 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
require './app'
run Application
Binary file added excalidraw_design.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading