diff --git a/Gemfile b/Gemfile index b1a320395a..36ed6a484b 100644 --- a/Gemfile +++ b/Gemfile @@ -11,3 +11,12 @@ end group :development, :test do gem 'rubocop', '1.20' end + +gem "pg", "~> 1.5" + +gem "sinatra", "~> 3.0" +gem "sinatra-contrib", "~> 3.0" +gem "webrick", "~> 1.8" +gem "rack-test", "~> 2.1" + +gem "bcrypt", "~> 3.1" diff --git a/Gemfile.lock b/Gemfile.lock index 66064703c7..f413380a48 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.5.3) + rack (2.2.7) + 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,21 +57,40 @@ 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 (~> 3.1) + pg (~> 1.5) + 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 BUNDLED WITH - 2.2.26 + 2.4.12 diff --git a/app.rb b/app.rb new file mode 100644 index 0000000000..fa84c95861 --- /dev/null +++ b/app.rb @@ -0,0 +1,180 @@ +require 'sinatra/base' +require 'sinatra/reloader' +require_relative 'lib/peep_repository' +require_relative 'lib/maker_repository' +require_relative 'lib/database_connection' + +DatabaseConnection.connect + +class Application < Sinatra::Base + + enable :sessions + + configure :development do + register Sinatra::Reloader + also_reload 'lib/maker_repository' + also_reload 'lib/peep_repository' + end + + get '/' do + return erb(:homepage) + end + + get '/peeps' do + peep_repo = PeepRepository.new + @maker_repo = MakerRepository.new + @all_peeps = peep_repo.all.sort_by(&:date_posted).reverse + return erb(:peeps) + end + + get '/peeps/:id' do + if session[:id].nil? + return erb(:user_peeps_no_session) + else + peep_repo = PeepRepository.new + maker_repo = MakerRepository.new + @maker = maker_repo.find(session[:id]) + @makers_peeps = peep_repo.by_maker(session[:id]).sort_by(&:date_posted).reverse + return erb(:user_peeps) + end + end + + get '/signup' do + return erb(:signuppage) + end + + post '/signup' do + repo = MakerRepository.new + maker = Maker.new + maker.name = params[:name].strip + maker.username = params[:username].strip + maker.email_address = params[:email_address].strip + maker.password = params[:password].strip + + if signup_param_validation(maker.name, maker.username, maker.email_address, maker.password) + status 400 + else + repo.create(maker) + redirect '/loginpage' + end + end + + get '/loginpage' do + return erb(:loginpage) + end + + post '/loginpage' do + username = params[:username] + password = params[:password] + repo = MakerRepository.new + @maker = repo.find_by_username(username) + + stored_password = BCrypt::Password.new(@maker.password) + + if stored_password == password + session[:id] = @maker.id + return erb(:userpage) + else + return erb(:login_error) + end + + # if @maker.password == password + # session[:id] = @maker.id + # return erb(:userpage) + # else + # return erb(:login_error) + # end + end + + get '/userpage' do + if session[:id].nil? + return erb(:loginpage) + else + repo = MakerRepository.new + @maker = repo.find(session[:id]) + return erb(:userpage) + end + end + + get '/peep/new' do + if session[:id].nil? + return erb(:post_peep_no_session) + else + return erb(:new_peep) + end + end + + post '/peep/new' do + time = Time.new + repo = PeepRepository.new + peep = Peep.new + peep.title = params[:title].strip + peep.content = params[:content].strip + peep.date_posted = time + peep.maker_id = session[:id] + + if title_valid?(peep.title) + status 400 + else + repo.create(peep) + return erb(:peep_created) + end + end + + get '/delete_peep' do + if session[:id].nil? + return erb(:delete_no_session) + else + return erb(:delete_peep) + end + end + + post '/delete_peep' do + title = params[:title] + repo = PeepRepository.new + @selected = repo.find_by_title(title) + + if @selected.maker_id != session[:id] + status 400 # add an error stating you cannot delete a peep which you have not posted. + else + id = @selected.id + repo.delete(id) + return erb(:peep_deleted) + end + end + + get '/update_details' do + if session[:id].nil? + return erb(:update_no_session) + else + return erb(:update_maker) + end + end + + post '/update_details' do + @new_name = params[:name] + repo = MakerRepository.new + maker = repo.find(session[:id]) + maker.name = @new_name + repo.update(maker) + return erb(:updated_maker) + end + + get '/logout' do + session[:id] = nil + return redirect('/') + end + + def title_valid?(title) + repo = PeepRepository.new + return repo.all.any? { |row| row.title == title } || title.empty? || title.nil? + end + + def signup_param_validation(name, username, email_address, password) + repo = MakerRepository.new + all = [name, username, email_address, password] + return (repo.all.any? { |row| row.username == username } || + repo.all.any? { |row| row.email_address == email_address } || + all.any?(nil) || all.any?(&:empty?)) + end +end diff --git a/config.ru b/config.ru new file mode 100644 index 0000000000..af14ef717e --- /dev/null +++ b/config.ru @@ -0,0 +1,2 @@ +require './app' +run Application diff --git a/lib/database_connection.rb b/lib/database_connection.rb new file mode 100644 index 0000000000..3d2a00d6a0 --- /dev/null +++ b/lib/database_connection.rb @@ -0,0 +1,21 @@ +require 'pg' + +class DatabaseConnection + def self.connect + if ENV['ENV'] == 'test' + database_name = 'chitter_base_test' + else + database_name = 'chitter_base' + end + @connection = PG.connect({ host: '127.0.0.1', dbname: database_name }) + end + + 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/maker.rb b/lib/maker.rb new file mode 100644 index 0000000000..049edb5d9c --- /dev/null +++ b/lib/maker.rb @@ -0,0 +1,3 @@ +class Maker + attr_accessor :id, :name, :username, :email_address, :password +end diff --git a/lib/maker_repository.rb b/lib/maker_repository.rb new file mode 100644 index 0000000000..3c61694bff --- /dev/null +++ b/lib/maker_repository.rb @@ -0,0 +1,57 @@ +require_relative 'maker' +require 'bcrypt' + +class MakerRepository + + def all + result = DatabaseConnection.exec_params('SELECT * FROM makers;', []) + makers = [] + result.each do |row| + maker = Maker.new + maker.id = row['id'].to_i + maker.name = row['name'] + maker.username = row['username'] + maker.email_address = row['email_address'] + maker.password = row['password'] + makers << maker + end + makers + end + + def find_by_username(username) + sql = 'SELECT * FROM makers WHERE username = $1;' + result = DatabaseConnection.exec_params(sql, [username]) + maker = Maker.new + maker.id = result[0]['id'].to_i + maker.name = result[0]['name'] + maker.username = result[0]['username'] + maker.email_address = result[0]['email_address'] + maker.password = result[0]['password'] + return maker + end + + def find(id) + sql = 'SELECT * FROM makers WHERE id = $1;' + result = DatabaseConnection.exec_params(sql, [id]) + maker = Maker.new + maker.id = result[0]['id'].to_i + maker.name = result[0]['name'] + maker.username = result[0]['username'] + maker.email_address = result[0]['email_address'] + maker.password = result[0]['password'] + return maker + end + + def create(maker_obj) + encrypted_password = BCrypt::Password.create(maker_obj.password) + sql = 'INSERT INTO makers(name, username, email_address, password) VALUES($1, $2, $3, $4);' + params = [maker_obj.name, maker_obj.username, maker_obj.email_address, encrypted_password] + result = DatabaseConnection.exec_params(sql, params) + end + + def update(maker) + sql = 'UPDATE makers SET name = $1 WHERE id = $2;' + params = [maker.name, maker.id] + update = DatabaseConnection.exec_params(sql, params) + end +end diff --git a/lib/peep.rb b/lib/peep.rb new file mode 100644 index 0000000000..3fdaf4f4a1 --- /dev/null +++ b/lib/peep.rb @@ -0,0 +1,3 @@ +class Peep + attr_accessor :id, :title, :content, :date_posted, :maker_id +end diff --git a/lib/peep_repository.rb b/lib/peep_repository.rb new file mode 100644 index 0000000000..a2ec27d5c0 --- /dev/null +++ b/lib/peep_repository.rb @@ -0,0 +1,57 @@ +require_relative 'peep' + +class PeepRepository + + def all + result = DatabaseConnection.exec_params('SELECT * FROM peeps;', []) + peeps = [] + result.each do |row| + peep = Peep.new + peep.id = row['id'].to_i + peep.title = row['title'] + peep.content = row['content'] + peep.date_posted = row['date_posted'] + peep.maker_id = row['maker_id'].to_i + peeps << peep + end + peeps + end + + def by_maker(maker_id) + result = DatabaseConnection.exec_params('SELECT * FROM peeps WHERE maker_id = $1;', [maker_id]) + maker_peeps = [] + result.each do |row| + peep = Peep.new + peep.id = row['id'].to_i + peep.title = row['title'] + peep.content = row['content'] + peep.date_posted = row['date_posted'] + peep.maker_id = row['maker_id'].to_i + maker_peeps << peep + end + maker_peeps + end + + def find_by_title(title) + result = DatabaseConnection.exec_params('SELECT * FROM peeps WHERE title = $1;', [title]) + peep = Peep.new + peep.id = result[0]['id'].to_i + peep.title = result[0]['title'] + peep.content = result[0]['content'] + peep.date_posted = result[0]['date_posted'] + peep.maker_id = result[0]['maker_id'].to_i + return peep + end + + def create(peep_obj) + sql = 'INSERT INTO peeps(title, content, date_posted, maker_id) VALUES($1, $2, $3, $4);' + params = [peep_obj.title, peep_obj.content, peep_obj.date_posted, peep_obj.maker_id] + result = DatabaseConnection.exec_params(sql, params) + end + + def delete(id) + sql = 'DELETE FROM peeps WHERE id = $1;' + params = [id] + result = DatabaseConnection.exec_params(sql, params) + end +end diff --git a/public/bob.jpg b/public/bob.jpg new file mode 100644 index 0000000000..acd373addf Binary files /dev/null and b/public/bob.jpg differ diff --git a/public/logo.jpg b/public/logo.jpg new file mode 100644 index 0000000000..9e24cdd553 Binary files /dev/null and b/public/logo.jpg differ diff --git a/public/style.css b/public/style.css new file mode 100644 index 0000000000..040523f8fa --- /dev/null +++ b/public/style.css @@ -0,0 +1,37 @@ +head { + background-color: lightblue; + text-align: center; +} + +body { + background-color: lightblue; + text-align: center; + padding: 10px; + border: 5px; + font-size: 20px; + font-family: fantasy; +} + +h1 { + color: #2982ff; + border: 3px solid #2982ff; + padding: 10px; + font-family:'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; +} + +button { + font-size: 20px; + background-color: #2982ff; + color: lightblue; + border-radius: 12px; + border-color: #2982ff; + padding: 10px 20px; + border: none; + text-decoration: underline; + transition-duration: 0.3s; + cursor: pointer; +} + +button:hover { + background-color: lightblue; +} diff --git a/recipe.md b/recipe.md new file mode 100644 index 0000000000..91173ca0db --- /dev/null +++ b/recipe.md @@ -0,0 +1,294 @@ +# Chitter Model and Repository Classes Design Recipe + +## 1. Design and Create the table + +User Stories/specification: + +> As a Maker +> So that I can let people know what I am doing +> I want to post a message (peep) to chitter + +> As a maker +> So that I can see what others are saying +> I want to see all peeps in reverse chronological order + +> As a Maker +> So that I can better appreciate the context of a peep +> I want to see the time at which it was made + +> As a Maker +> So that I can post messages on Chitter as me +> I want to sign up for Chitter + +> As a Maker +> So that only I can post messages on Chitter as me +> I want to log in to Chitter + +> As a Maker +> So that I can avoid others posting messages on Chitter as me +> I want to log out of Chitter + + +#### relationship: + +1. Can one Maker have many Peeps ? YES + +-> A Maker HAS MANY Peeps +-> A Peep BELONGS TO a Maker +-> Therefore, the foreign key is on the Peeps table. + +Table design: +Makers: id | name | username | email_address | password +Peeps: id | title | content | date_posted | maker_id + +id: SERIAL +name: text +username: text +email_address: text +password: text + +id: SERIAL +title: text +content: text +posted: timestamp +maker_id: int + + +#### Creating the tables: + +```sql +CREATE TABLE makers ( + id SERIAL PRIMARY KEY, + name text, + username text, + email_address text, + password text +); + +CREATE TABLE peeps ( + id SERIAL PRIMARY KEY, + title text, + content text, + date_posted timestamp, + maker_id int, + constraint fk_maker foreign key (maker_id) + references makers(id) + on delete cascade +); +``` + +Create table: +psql -h 127.0.0.1 chitter_base < DB_table_setup.sql + + +## 2. Create the SQL seeds +```sql +-- file: spec/chitter_seeds.sql) + +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; + +INSERT INTO makers(name, username, email_address, password) VALUES('Matty Boi', 'MattyMooMilk', 'mattys_fake_email@tiscali.net', 'Password1!'); +INSERT INTO makers(name, username, email_address, password) VALUES('Hayley Lady', 'HayleyOk', 'another_fake_email420@gmail.com', 'DifferentPassword123.'); + + +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('My first ever peep', 'Internet is crazy', '12-12-2023 04:05:06', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys peep', 'Another test peep', '12-12-2023 04:05:06', 2); + +``` + +psql -h 127.0.0.1 your_database_name < seeds_{table_name}.sql + +## 3. Define the class names + +```ruby +# Model class' +# (in lib/maker.rb) + +class Maker +end + +# (in lib/peep.rb) +class Peep +end + +# Repository class' +# (in lib/maker_repository.rb) +class MakerRepository +end + +# (in lib/peep_repository.rb) +class PeepRepository +end +``` + +## 4. Implement the Model class' + +``` ruby + +# Model class +# (in lib/maker.rb) + +class Maker + attr_accessor :id, :name, :username, :email_address, :password +end + +# Model class 2 +# (in lib/peep.rb) + +class Peep + attr_accessor :id, :title, :content, :date_posted, :maker_id +end + +``` + +## 5. Define the Repository Class' interface + +```ruby + +# Table name: makers +# Repository class +# (in lib/maker_repository.rb) + +class MakerRepository + + # Selecting all records + def all + # Executes the SQL query: + # SELECT * FROM makers; + # Returns all records as an array of user objects. - to be used by other methods in app.rb. + end + + # Adds a maker/user to the makers table in DB + def create(maker_obj) + # adds a user object to the database + # 'INSERT INTO makers(name, username, email_address, password) VALUES($1, $2, $3, $4);' + # params = [maker_obj.name, maker_obj.username, maker_obj.email_address, maker_obj.password] + # No return + end +end + +#Table name: peeps +#This repository class acts on order objects +# (in lib/peep_repository.rb) +class PeepRepository + + def all + # Returns an array of all peep objects + # Method can be called by other methods later (Will need to sort peeps by timestamp) + # Executes the SQL query: + # SELECT * FROM peeps; + end + + # Adds a new peep to the database: + def create(peep_obj) + # Adds a postobj to the posts table if post corresponds to existing user + # 'INSERT INTO peeps(title, content, date_posted, maker_id) VALUES($1, $2, $3, $4);' + # params = [peep_obj.title, peep_obj.content, peep_obj.date_posted, peep_obj.maker_id] + # No return + end + +# Deletes a peep from the database + def delete(id) + # 'DELETE FROM peeps WHERE id = $1;' + # params = [id] + end +end +``` + +## 6. Write Test Examples +These examples will later be encoded as RSpec tests. + +```ruby + +# EXAMPLES + +# 1. Get all Peeps +repo = PeepRepository.new +repo.all.length => # returns correct integer dependent on how many rows are in DB +repo.all.first.id => #always will return 1 +repo.all.last.id => #should return the same int as first test line +repo[2].title => #returns the title of object at index 2 (assuming there are 3 objs in array) +repo[2].date_posted => #returns the correct timestamp. + +# Get all Makers: +repo = MakerRepository.new +repo.all.length => # returns correct integer dependent on how many rows are in DB +repo.all.first.id => #always will return 1 +repo.all.last.id => #should return the same int as first test line +repo[2].name => #returns the customer name of object at index 2 (assuming there are 3 objs in array) + + +# if database is empty should return => [] + + +# 2. Add a Maker to database (Create account) +repo = MakerRepository.new +maker_1 = Maker.new # => can be a double => double :maker, name: 'name', username: 'username', email_address: 'fake_email@gmail.com', password: 'Password123' +maker_1.name = 'name' +maker_1.username = 'username' +maker_1.email_address = 'fake_email@gmail.com' +maker_1.password = 'Password123' +repo.create(maker_1) +expect(repo.all.last.name).to eq 'name' +expect(repo.all.last.username).to eq 'username' +expect(repo.all.last.email_address).to eq 'fake_email@gmail.com' +expect(repo.all.last.password).to eq 'Password123' +expect(repo.all.length) # => an integer 1 greater than our current length +expect(repo.all.last.id).to eq # => ^ same integer as previous expect line ^ + +# Add a peep to database (Create a peep) +repo = PeepRepository.new +peep = Peep.new # => can be a double +peep.title = '' +peep.content = '' +peep.date_posted = '' +peep.maker_id = 1 +repo.create(peep) +expect(repo.all.last.content).to eq +expect(repo.all.last.date_posted).to eq +expect(repo.all.length) # => an integer 1 greater than our current length +expect(repo.all.last.id).to eq # => ^ same integer as previous expect line ^ + +# Delete a peep +repo = PeepRepository.new +repo.delete(1) +expect(repo.all.length) # => an integer 1 fewer than our current length +expect(repo.all.first.id).to eq # => 2 + +``` + +## 7. Reload the SQL seeds before each test run +Running the SQL code present in the seed file will empty the table and re-insert the seed data. + +```ruby + +# file: spec/maker_repository_spec.rb + + def reset_makers_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_makers_table + end +end + +# file: spec/peep_repository_spec.rb + + def reset_peeps_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_peeps_table + end +end +``` + +## 8. Test-drive and implement the Repository class behaviour +_After each test you write, follow the test-driving process of red, green, refactor to implement the behaviour._ \ No newline at end of file diff --git a/routes_recipe.md b/routes_recipe.md new file mode 100644 index 0000000000..9812681896 --- /dev/null +++ b/routes_recipe.md @@ -0,0 +1,29 @@ + +Homepage: +> "Welcome to Chitter" - banner/header +> "Log in" - href to a log in form that allows a user to log into an existing account - GET +> "Sign Up" - href hyperlink to a sign up form which creates a new Maker obj and adds to DB - POST +> "View all peeps" - GET - Displays all existing peeps from all Makers + +Sign up page: +> form with all required fields to add a new - POST +> If successful takes to a newly created "User page" +> Failures: If username or email_address already exist within DB. + +Log in page: +> Form with username and password sections - GET +> If successful takes to an existing "User page" +> Failures: If email_address or username used for log in does not exist in database +If password does not correspond with existing username/email_address + +User page: +> "Welcome #{user/maker}" +> "Create a peep" - POST - adds a peep to the database related to this user/maker id +> "Delete a peep" - POST - deletes a peep from the database +> "View all previous peeps" - GET - Shows all peeps related to this user - params[id] +> "Log out" - GET - returns to homepage + +All peeps page: +> (Does not require an existing account to view all peeps) +> Displays all existing peeps in order of most latest timestamp. +> Each peep shows name of maker who created it, their username and a timestamp. diff --git a/spec/chitter_seeds.sql b/spec/chitter_seeds.sql new file mode 100644 index 0000000000..ee130efb27 --- /dev/null +++ b/spec/chitter_seeds.sql @@ -0,0 +1,10 @@ +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; + +INSERT INTO makers(name, username, email_address, password) VALUES('Matty Boi', 'MattyMooMilk', 'mattys_fake_email@tiscali.net', 'Password1!'); +INSERT INTO makers(name, username, email_address, password) VALUES('Hayley Lady', 'HayleyOk', 'another_fake_email420@gmail.com', 'DifferentPassword123.'); + +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('My first ever peep', 'Internet is crazy', '2023-07-10 04:05:06 +0000', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys peep', 'Another test peep', '2023-07-21 12:25:12 +0000', 2); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('A second peep', 'Theres a peep party now', '2023-06-07 13:17:26 +0000', 1); +INSERT INTO peeps(title, content, date_posted, maker_id) VALUES('Hayleys alternate peep', 'peep to be deleted', '2023-06-29 17:44:43 +0000', 2); diff --git a/spec/empty_tables.sql b/spec/empty_tables.sql new file mode 100644 index 0000000000..881d296c9c --- /dev/null +++ b/spec/empty_tables.sql @@ -0,0 +1,2 @@ +TRUNCATE TABLE makers RESTART IDENTITY CASCADE; +TRUNCATE TABLE peeps RESTART IDENTITY CASCADE; diff --git a/spec/integration/app_spec.rb b/spec/integration/app_spec.rb new file mode 100644 index 0000000000..38df86441a --- /dev/null +++ b/spec/integration/app_spec.rb @@ -0,0 +1,326 @@ +require "spec_helper" +require "rack/test" +require_relative '../../app' + +describe Application do + + include Rack::Test::Methods + let(:app) { Application.new } + + context '/' do + it 'Should return a html formatted web page including a welcome banner' do + response = get('/') + expect(response.status).to eq(200) + expect(response.body).to include ('

Welcome to the Chitter-Sphere!

') + end + + it 'Should include a photograph/graphic' do + response = get('/') + expect(response.body).to include ('') + end + + it 'Should include 5 different hyperlinks' do + response = get('/') + expect(response.status).to eq (200) + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include('') + end + end + + context '/peeps' do + it 'Should display all peeps from all makers within the peeps database formatted in HTML' do + response = get('/peeps') + expect(response.status).to eq (200) + expect(response.body).to include ('HayleyOk') + expect(response.body).to include ('Another test peep') + expect(response.body).to include ('2023-07-21 12:25:12') + end + end + + context '/loginpage' do + it 'Should return a login submit form' do + response = get('/loginpage') + expect(response.status).to eq (200) + expect(response.body).to include ('
') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + end + + it 'Should return an error page when username is not present within the DB' do + response = post('/loginpage', username: 'FakeUsername', password: 'Password1!') + expect(response.status).to eq(500) # add an alternate fail test here. + end + + it 'Should return an alternate/error page when password is incorrect' do + response = post('/signup', name: 'Tom Segura', username: 'TommyBuns', email_address: 'YMH@mail.com', password: 'YMH') + response = post('/loginpage', username: 'TommyBuns', password: 'cabbage') + expect(response.status).to eq(200) + expect(response.body).to include ('Login unsuccessful') + expect(response.body).to include ('

Please return to the login page and try again

') + expect(response.body).to include ('Login page') + end + + it 'Should return a welcome banner with the correct users name' do + response = post('/signup', name: 'Harry Potter', username: 'BigPott', email_address: 'phillosopher@mail.com', password: 'SickScar') + response = post('/loginpage', username: 'BigPott', password: 'SickScar') + expect(response.status).to eq(200) + expect(response.body).to include ('Harry Potter') + end + + it 'Should return a welcome banner with the correct users name' do + response = post('/signup', name: 'Kirk Hammett', username: 'KingKirk', email_address: 'shredding@mail.com', password: 'shreddster') + response = post('/loginpage', username: 'KingKirk', password: 'shreddster') + expect(response.status).to eq(200) + expect(response.body).to include ('Kirk Hammett') + end + + it 'Should return a list of user options as hyperlinks' do + response = post('/signup', name: 'Some King of User', username: 'SomeKindOfUser', email_address: 'metallica@mail.com', password: 'hetfield') + response = post('/loginpage', username: 'SomeKindOfUser', password: 'hetfield') + expect(response.status).to eq(200) + expect(response.body).to include ('Post a new peep') + expect(response.body).to include ('Delete a peep') + expect(response.body).to include ('View your previous peeps') + expect(response.body).to include ('Log out') + end + end + + context '/signup' do + it 'Should return a sign up form to add a new maker to the database' do + response = get('/signup') + expect(response.status).to eq (200) + expect(response.body).to include ('') + expect(response.body).to include ('

Create an account

') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + end + + it 'Should add a new maker to the database/create a maker account and then redirect to user page' do + response = post('/signup', name: 'Matt', username: 'mattmatttest', email_address: 'bigmatt44@gmail.com', password: 'ValidPassword12') + expect(response.status).to eq (302) + end + + it 'Should return an error when username already exists in database' do + response = post('/signup', name: 'Hayley A', username: 'HayleyOk', email_address: 'hayleyhayley@tiscali.net', password: 'AnotherPass1') + expect(response.status).to eq (400) + end + + it 'Should return an error when the email address already exists in the database' do + response = post('/signup', name: 'Hayley A', username: 'HayleyAlt', email_address: 'another_fake_email420@gmail.com', password: 'AnotherPass1') + expect(response.status).to eq (400) + end + + it 'Should return an error when a nil value is entered' do + response = post('/signup', name: '', username: 'MattAlt', email_address: 'mattaltfakeemail@gmail.com', password: 'AnotherPass1') + expect(response.status).to eq (400) + end + + it 'Should return an error when an input is empty' do + response = post('/signup', name: 'Matt H', username: ' ', email_address: 'fakestofthemall@testmail.com', password: 'AnotherPass1') + expect(response.status).to eq (400) + end + end + + context 'peep/new' do + it 'Should return an error page with hyperlinks to log in if user is not logged in already' do + response = get('/peep/new') + expect(response.status).to eq (200) + expect(response.body).to include ('

To post a new peep please login or create an account

') + expect(response.body).to include ('Sign up page') + expect(response.body).to include ('Login page') + end + + it 'Should return a form page to create a new peep if the user is logged in' do + response = post('/signup', name: 'Lars Ulrich', username: 'Lars', email_address: 'napster@mail.com', password: 'lars') + response = post('/loginpage', username: 'Lars', password: 'lars') + response = get('/peep/new') + expect(response.status).to eq (200) + expect(response.body).to include ('

Post a new peep to Chitter

') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + end + + # Check these tests: + it 'Should only allow users logged into their account to successfully create new peeps' do + response = post('/loginpage', username: 'MattyMooMilk', password: 'Password1!') + response = post('/peep/new', title: 'new title', content: 'This is some content yes') + expect(response.status).to eq (200) + expect(response.body).to include ('

Your Peep was successfully posted!

') + expect(response.body).to include ('Your account') + expect(response.body).to include ('Homepage') + end + + it 'Should return an error if the title of the peep already exists in database' do + response = post('/loginpage', username: 'MattyMooMilk', password: 'Password1!') + response = post('/peep/new', title: 'A second peep', content: 'This is some fake content') + expect(response.status).to eq (400) + end + end + + # Check these tests: + context '/peeps/:id' do + it 'Should return all peeps by passed maker in a formatted HTML list only if user is logged in' do + response = post('/signup', name: 'Severus Snape', username: 'SnapeSnape', email_address: 'halfblood@mail.com', password: 'halfit') + response = post('/loginpage', username: 'SnapeSnape', password: 'halfit') + response = post('/peep/new', title: 'This is a test peep', content: 'A test peep') + response = get('/peeps/1') + expect(response.status).to eq (200) + expect(response.body).to include ('Severus Snapes Peeps') + expect(response.body).to include ('This is a test peep') + end + + it 'Should return all peeps by passed maker in a formatted HTML list only if user is logged in' do + response = post('/signup', name: 'Fake User', username: 'faketest', email_address: 'faketestemail@gmail.com', password: 'fakepassword') + response = post('/loginpage', username: 'faketest', password: 'fakepassword') + response = post('/peep/new', title: 'A new pretend peep', content: 'This is some content') + response = get('/peeps/1') + expect(response.status).to eq (200) + expect(response.body).to include ('Fake Users Peeps') + expect(response.body).to include ('A new pretend peep') + expect(response.body).to include ('This is some content') + end + + it 'Should return the log in page if user is not logged in/no active session' do + response = get('/peeps/1') + expect(response.body).to include ('

Login required

') + expect(response.body).to include ('

To view all of your existing peeps please login or create an account to start peeping

') + expect(response.body).to include ('Login page') + end + end + + context '/logout' do + it 'Should redirect user to the homepage' do + response = post('/signup', name: 'Logout user', username: 'logout', email_address: 'logout@gmail.com', password: 'validpassword') + response = post('/loginpage', username: 'logout', password: 'validpassword') + response = get('/logout') + expect(response.status).to eq (302) + end + + it 'Should return the homepage with the session set to nil - This will prevent user from posting a peep' do + response = post('/loginpage', username: 'HayleyOk', password: 'DifferentPassword123.') + response = get('/logout') + response = get('/peep/new') + expect(response.status).to eq (200) + expect(response.body).to include ('

To post a new peep please login or create an account

') + expect(response.body).to include ('Sign up page') + expect(response.body).to include ('Login page') + end + + it 'Should return the homepage with the session set to nil - This will prevent user from deleting a peep' do + response = post('/signup', name: 'User', username: 'NewUser8', email_address: 'newuseremail8@mail.com', password: 'Password8') + response = post('/loginpage', username: 'NewUser8', password: 'Password8') + response = get('/logout') + response = get('/delete_peep') + expect(response.body).to include ('

To delete an exiting peep of yours, please login or create an account

') + expect(response.body).to include ('Sign up page') + expect(response.body).to include ('Login page') + end + end + + context '/delete_peep' do + it 'Should only allow the user to delete a peep if they are logged into their account' do + response = post('/signup', name: 'User', username: 'NewUser7', email_address: 'newuseremail7@mail.com', password: 'Password7') + response = post('/loginpage', username: 'NewUser7', password: 'Password7') + response = get('/delete_peep') + expect(response.body).to include ('

Delete a selected peep

') + expect(response.body).to include ('

Please input the title of the peep you would like to delete

') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + end + + it 'Should send the user to a new log in page if the user attempts to delete a peep without logging in' do + response = get('/delete_peep') + expect(response.status).to eq (200) + expect(response.body).to include ('Delete a peep') + expect(response.body).to include ('

Unable to delete a peep

') + expect(response.body).to include ('

To delete an exiting peep of yours, please login or create an account

') + expect(response.body).to include ('Sign up page') + expect(response.body).to include ('Login page') + end + + it 'Should delete the selected peep if the peep belongs to the user' do + response = post('/signup', name: 'User', username: 'NewUser6', email_address: 'newuseremail6@mail.com', password: 'Password6') + response = post('/loginpage', username: 'NewUser6', password: 'Password6') + response = post('/peep/new', title: 'A fake title', content: 'A fake peep' ) + response = post('/delete_peep', title: 'A fake title') + expect(response.status).to eq (200) + expect(response.body).to include ('Peep deleted successfully') + expect(response.body).to include ('

The peep "A fake title" was removed from chitter

') + expect(response.body).to include ('Your account') + end + + it 'Should return a 400 error if user tries to delete a peep that they have not posted' do + response = post('/signup', name: 'User', username: 'NewUser5', email_address: 'newuseremail5@mail.com', password: 'Password5') + response = post('/loginpage', username: 'NewUser5', password: 'Password5') + response = post('/delete_peep', title: 'A second peep') + expect(response.status).to eq (400) + end + + it 'Should return a 400 error if the user tries to delete a peep that does not exist' do + response = post('/signup', name: 'User', username: 'NewUser4', email_address: 'newuseremail4@mail.com', password: 'Password4') + response = post('/loginpage', username: 'NewUser4', password: 'Password4') + response = post('/delete_peep', title: 'Non existent peep') + expect(response.status).to eq (500) # Add an alternate fail here for this scenario. + end + end + + context '/update_details' do + it 'Should return a form page to update details if user is logged in' do + response = post('/signup', name: 'User', username: 'NewUser3', email_address: 'newuseremail3@mail.com', password: 'Password') + response = post('/loginpage', username: 'NewUser3', password: 'Password') + response = get('/update_details') + expect(response.status).to eq (200) + expect(response.body).to include ('

Update your account details

') + expect(response.body).to include ('') + expect(response.body).to include ('') + expect(response.body).to include ('') + end + + it 'Should redirect the user to a login style page if user is not logged in' do + response = get('/update_details') + expect(response.status).to eq (200) + expect(response.body).to include ('

Unable to update your details

') + expect(response.body).to include ('

To update your account details you must be logged in

') + expect(response.body).to include ('Login page') + end + + it 'Should update the users name to new name' do + response = post('/signup', name: 'Useragain', username: 'Useragain', email_address: 'email2@email.com', password: 'Passwordzz') + response = post('/loginpage', username: 'Useragain', password: 'Passwordzz') + response = post('/update_details', name: 'Hayley 2') + expect(response.status).to eq (200) + expect(response.body).to include ('

Name successfully updated

') + expect(response.body).to include ('You have updated your name to Hayley 2') + expect(response.body).to include ('Your account') + end + end + + context '/userpage' do + it 'Should not allow user to enter user page if not logged in' do + response = get('/userpage') + expect(response.status).to eq (200) + end + + it 'Should return the userpage when user is logged in' do + response = post('/signup', name: 'UserUser', username: 'NewUser1', email_address: 'emailok@email.com', password: 'Passwordpreenc') + response = post('/loginpage', username: 'NewUser1', password: 'Passwordpreenc') + response = get('/userpage') + expect(response.status).to eq (200) + expect(response.body).to include ('Homepage') + expect(response.body).to include ('Post a new peep') + expect(response.body).to include ('Delete a peep') + expect(response.body).to include ('View your previous peeps') + expect(response.body).to include ('Update/change your details') + expect(response.body).to include ('Log out') + end + end +end diff --git a/spec/maker_repository_spec.rb b/spec/maker_repository_spec.rb new file mode 100644 index 0000000000..af493873cc --- /dev/null +++ b/spec/maker_repository_spec.rb @@ -0,0 +1,68 @@ +require 'maker_repository' +require 'bcrypt' + +describe MakerRepository do + + def reset_makers_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_makers_table + end + + context 'retreiving all data from Database' do + it 'Should return an array of all makers from database when all method is called' do + repo = MakerRepository.new + expect(repo.all.length).to eq (2) + expect(repo.all.first.id).to eq (1) + expect(repo.all.last.id).to eq (2) + expect(repo.all[1].name).to eq ('Hayley Lady') + expect(repo.all[1].password).to eq ('DifferentPassword123.') + end + end + + context 'Retreiving a single object from the database' do + it 'Should return the chosen maker that corresponds with passed id' do + repo = MakerRepository.new + selected = repo.find(1) + expect(selected.name).to eq ('Matty Boi') + expect(selected.username).to eq ('MattyMooMilk') + end + + it 'Should return the chosen maker that corresponds with passed username' do + repo = MakerRepository.new + maker = repo.find_by_username('MattyMooMilk') + expect(maker.id).to eq (1) + expect(maker.email_address).to eq ('mattys_fake_email@tiscali.net') + expect(maker.password).to eq ('Password1!') + end + end + + context 'Creating new makers' do + it 'Should add a new maker to the makers database when the create method is passed an acceptable obj' do + repo = MakerRepository.new + maker = double :maker, name: 'name', username: 'username', email_address: 'fake_email@gmail.com', password: 'Password123' + repo.create(maker) + expect(repo.all.last.name).to eq ('name') + expect(repo.all.last.username).to eq ('username') + expect(repo.all.last.email_address).to eq ('fake_email@gmail.com') + expect(BCrypt::Password.new(repo.all.last.password)).to eq ('Password123') + expect(repo.all.length).to eq (3) + expect(repo.all.last.id).to eq (3) + end + end + + context 'Updating a makers name' do + it 'Should update the name of the user' do + repo = MakerRepository.new + maker = repo.find(1) + maker.name = "Updated name" + repo.update(maker) + updated = repo.find(1) + expect(updated.name).to eq ('Updated name') + end + end +end diff --git a/spec/peep_repository_spec.rb b/spec/peep_repository_spec.rb new file mode 100644 index 0000000000..0aee0a93ec --- /dev/null +++ b/spec/peep_repository_spec.rb @@ -0,0 +1,65 @@ +require 'peep_repository' + +describe PeepRepository do + + def reset_peeps_table + seed_sql = File.read('spec/chitter_seeds.sql') + connection = PG.connect({ host: '127.0.0.1', dbname: 'chitter_base_test' }) + connection.exec(seed_sql) + end + + before(:each) do + reset_peeps_table + end + + context 'all' do + it 'Should return all peeps from database' do + repo = PeepRepository.new + expect(repo.all.length).to eq(4) + expect(repo.all.first.id).to eq(1) + expect(repo.all.last.id).to eq(4) + expect(repo.all[1].title).to eq('Hayleys peep') + expect(repo.all[1].date_posted).to eq('2023-07-21 12:25:12') + end + end + + context 'Peeps by Maker' do + it 'should only show peeps by selected Maker' do + repo = PeepRepository.new + expect(repo.by_maker(2).length).to eq (2) + expect(repo.by_maker(2)[0].maker_id).to eq (2) + expect(repo.by_maker(2)[0].content).to eq ('Another test peep') + end + end + + context 'peeps by title' do + it 'Should select and return a peep from the database by its given title' do + repo = PeepRepository.new + selected = repo.find_by_title('Hayleys alternate peep') + expect(selected.content).to eq ('peep to be deleted') + expect(selected.maker_id).to eq (2) + end + end + + context 'create' do + it 'Should add a new peep to the database' do + repo = PeepRepository.new + peep = double :peep, title: 'A double peep', content: 'double peeps content', date_posted: '2023-12-04 10:45:32', maker_id: 2 + repo.create(peep) + expect(repo.all.last.content).to eq('double peeps content') + expect(repo.all.last.date_posted).to eq('2023-12-04 10:45:32') + expect(repo.all.length).to eq(5) + expect(repo.all.last.id).to eq(5) + expect(repo.all.last.maker_id).to eq(2) + end + end + + context 'delete' do + it 'Should remove the row from the database that corresponds with passed id' do + repo = PeepRepository.new + repo.delete(1) + expect(repo.all.length).to eq(3) + expect(repo.all.first.id).to eq (2) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 252747d899..69d9d7c341 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,10 @@ require 'simplecov' require 'simplecov-console' +require 'database_connection' + +ENV['ENV'] = 'test' + +DatabaseConnection.connect SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([ SimpleCov::Formatter::Console, diff --git a/spec/table_design_seeds.sql b/spec/table_design_seeds.sql new file mode 100644 index 0000000000..334673bbf0 --- /dev/null +++ b/spec/table_design_seeds.sql @@ -0,0 +1,18 @@ +CREATE TABLE makers ( + id SERIAL PRIMARY KEY, + name text, + username text, + email_address text, + password text +); + +CREATE TABLE peeps ( + id SERIAL PRIMARY KEY, + title text, + content text, + date_posted timestamp, + maker_id int, + constraint fk_maker foreign key (maker_id) + references makers(id) + on delete cascade +); \ No newline at end of file diff --git a/views/delete_no_session.erb b/views/delete_no_session.erb new file mode 100644 index 0000000000..0b425f2780 --- /dev/null +++ b/views/delete_no_session.erb @@ -0,0 +1,17 @@ + + + Delete a peep + + +

Unable to delete a peep

+
+

To delete an exiting peep of yours, please login or create an account

+
+
+ Sign up page +
+
+ Login page +
+ + \ No newline at end of file diff --git a/views/delete_peep.erb b/views/delete_peep.erb new file mode 100644 index 0000000000..12226e7931 --- /dev/null +++ b/views/delete_peep.erb @@ -0,0 +1,20 @@ + + + Delete a peep +

Delete a selected peep

+ + +
+

Please input the title of the peep you would like to delete

+
+ +
+ +
+
+ +
+ +
+ + diff --git a/views/homepage.erb b/views/homepage.erb new file mode 100644 index 0000000000..215a4f9481 --- /dev/null +++ b/views/homepage.erb @@ -0,0 +1,27 @@ + + + No one is safe in the Chitter-Sphere + +

Welcome to the Chitter-Sphere!

+ + +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+ + diff --git a/views/login_error.erb b/views/login_error.erb new file mode 100644 index 0000000000..3f3a5e7703 --- /dev/null +++ b/views/login_error.erb @@ -0,0 +1,14 @@ + + + Login unsuccessful + + +

Login Failed

+
+

Please return to the login page and try again

+
+
+ Login page +
+ + \ No newline at end of file diff --git a/views/loginpage.erb b/views/loginpage.erb new file mode 100644 index 0000000000..c372563b60 --- /dev/null +++ b/views/loginpage.erb @@ -0,0 +1,19 @@ + + + Login Page + + +

Login to your account

+
+
+ + +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/views/new_peep.erb b/views/new_peep.erb new file mode 100644 index 0000000000..04d4ec1b14 --- /dev/null +++ b/views/new_peep.erb @@ -0,0 +1,19 @@ + + + Write a new peep + + +

Post a new peep to Chitter

+
+
+ + +
+
+ + +
+ +
+ + diff --git a/views/peep_created.erb b/views/peep_created.erb new file mode 100644 index 0000000000..6b895ba713 --- /dev/null +++ b/views/peep_created.erb @@ -0,0 +1,16 @@ + + + Peep created successfully + + +
+

Your Peep was successfully posted!

+
+
+ Your account +
+
+ Homepage +
+ + diff --git a/views/peep_deleted.erb b/views/peep_deleted.erb new file mode 100644 index 0000000000..66c1c5a19f --- /dev/null +++ b/views/peep_deleted.erb @@ -0,0 +1,16 @@ + + + Peep deleted successfully + + +
+

The peep "<%= @selected.title %>" was removed from chitter

+
+
+ Your account +
+
+ Homepage +
+ + diff --git a/views/peeps.erb b/views/peeps.erb new file mode 100644 index 0000000000..89d2786556 --- /dev/null +++ b/views/peeps.erb @@ -0,0 +1,30 @@ + + + All Peeps + + +

Peeps

+
+

Total peeps: <%= @all_peeps.length %>

+
+
+ <% @all_peeps.each do |peep| %> +

+ <%= peep.title %> +

+

+ <%= peep.content %> +

+

+ <%= @maker_repo.find(peep.maker_id).username %> +

+

+ <%= peep.date_posted %> +

+

+ -------------------------- +

+ <% end %> +
+ + diff --git a/views/post_peep_no_session.erb b/views/post_peep_no_session.erb new file mode 100644 index 0000000000..aaac88819a --- /dev/null +++ b/views/post_peep_no_session.erb @@ -0,0 +1,17 @@ + + + Unable to post a new peep + + +

Login required

+
+

To post a new peep please login or create an account

+
+
+ Sign up page +
+
+ Login page +
+ + \ No newline at end of file diff --git a/views/signuppage.erb b/views/signuppage.erb new file mode 100644 index 0000000000..bbc7871afa --- /dev/null +++ b/views/signuppage.erb @@ -0,0 +1,27 @@ + + + Sign Up + + +

Create an account

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/views/update_maker.erb b/views/update_maker.erb new file mode 100644 index 0000000000..1a097c8b51 --- /dev/null +++ b/views/update_maker.erb @@ -0,0 +1,15 @@ + + + Update account + + +

Update your account details

+
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/views/update_no_session.erb b/views/update_no_session.erb new file mode 100644 index 0000000000..24bbdeadd0 --- /dev/null +++ b/views/update_no_session.erb @@ -0,0 +1,14 @@ + + + Update + + +

Unable to update your details

+
+

To update your account details you must be logged in

+
+
+ Login page +
+ + \ No newline at end of file diff --git a/views/updated_maker.erb b/views/updated_maker.erb new file mode 100644 index 0000000000..9bcf2cd37c --- /dev/null +++ b/views/updated_maker.erb @@ -0,0 +1,14 @@ + + + Update Successful + + +

Name successfully updated

+
+

You have updated your name to <%= @new_name %>

+
+
+ Your account +
+ + \ No newline at end of file diff --git a/views/user_peeps.erb b/views/user_peeps.erb new file mode 100644 index 0000000000..caeb112412 --- /dev/null +++ b/views/user_peeps.erb @@ -0,0 +1,24 @@ + + + All Peeps + + +

<%= @maker.name %>s Peeps

+
+ <% @makers_peeps.each do |peep| %> +

+ <%= peep.title %> +

+

+ <%= peep.content %> +

+

+ <%= peep.date_posted %> +

+

+ -------------------------- +

+ <% end %> +
+ + \ No newline at end of file diff --git a/views/user_peeps_no_session.erb b/views/user_peeps_no_session.erb new file mode 100644 index 0000000000..0563072f99 --- /dev/null +++ b/views/user_peeps_no_session.erb @@ -0,0 +1,17 @@ + + + Unable to view your peeps + + +

Login required

+
+

To view all of your existing peeps please login or create an account to start peeping

+
+
+ Sign up page +
+
+ Login page +
+ + \ No newline at end of file diff --git a/views/userpage.erb b/views/userpage.erb new file mode 100644 index 0000000000..c9eaf656ca --- /dev/null +++ b/views/userpage.erb @@ -0,0 +1,26 @@ + + + <%= @maker.name %> + + +

<%= @maker.name %>

+
+ Homepage +
+
+ Post a new peep +
+
+ Delete a peep +
+
+ View your previous peeps +
+
+ Update/change your details +
+
+ Log out +
+ +