diff --git a/Gemfile b/Gemfile index 66526d43..cfb1cb47 100644 --- a/Gemfile +++ b/Gemfile @@ -5,6 +5,13 @@ gem 'rspec', '~> 2.14.1' gem 'sinatra', '~> 1.4.5' gem 'sinatra-contrib', '~> 1.4.2' gem 'rest-client' +gem 'rack-flash3' +gem 'pg' +# added for rspec portion group block + +group :test do + gem 'rack-test' +end # Testing -gem 'pry-byebug' +gem 'pry-byebug' \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index bbeebb1e..12bd3520 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -14,6 +14,7 @@ GEM mime-types (1.25.1) multi_json (1.10.1) netrc (0.8.0) + pg (0.17.1) pry (0.10.1) coderay (~> 1.1.0) method_source (~> 0.8.1) @@ -22,6 +23,8 @@ GEM byebug (~> 3.4) pry (~> 0.10) rack (1.5.2) + rack-flash3 (1.0.5) + rack rack-protection (1.5.3) rack rack-test (0.6.2) @@ -55,7 +58,10 @@ PLATFORMS ruby DEPENDENCIES + pg pry-byebug + rack-flash3 + rack-test rest-client rspec (~> 2.14.1) sinatra (~> 1.4.5) diff --git a/lib/petshopserver.rb b/lib/petshopserver.rb new file mode 100644 index 00000000..bfaf0e61 --- /dev/null +++ b/lib/petshopserver.rb @@ -0,0 +1,151 @@ +require 'pg' + +require_relative 'petshopserver/repos/shops_repo.rb' +require_relative 'petshopserver/repos/cats_repo.rb' +require_relative 'petshopserver/repos/dogs_repo.rb' +require_relative 'petshopserver/repos/users_repo.rb' + +module Petshopserver + def self.create_db_connection(dbname) + PG.connect(host: 'localhost', dbname: dbname) + end + + def self.clear_db(db) + db.exec <<-SQL + DELETE FROM shops; + DELETE FROM dogs; + DELETE FROM cats; + DELETE FROM users; + 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 shops( + id SERIAL PRIMARY KEY, + name VARCHAR + ); + CREATE TABLE IF NOT EXISTS dogs( + id SERIAL PRIMARY KEY, + "shopId" int references shops(id), + name varchar, + "imageUrl" varchar, + happiness int, + adopted boolean + ); + CREATE TABLE IF NOT EXISTS cats( + id SERIAL PRIMARY KEY, + "shopId" int references shops(id), + name varchar, + "imageUrl" varchar, + adopted boolean + ); + CREATE TABLE IF NOT EXISTS userPets( + id SERIAL PRIMARY KEY, + "user_id" INTEGER references users(id), + "cat_id" INTEGER references cats(id), + "dog_id" INTEGER references dogs(id) + ); + SQL + end + + def self.seed_db(db) + db.exec <<-SQL + INSERT INTO users (username, password) values ('anonymous', 'anonymous') + # INSERT INTO users (username, password) values ('Jessica', '123') + SQL + end + + def self.drop_tables(db) + db.exec <<-SQL + DROP TABLE shops; + DROP TABLE dogs; + DROP TABLE cats; + DROP TABLE users; + SQL + end + + def self.seed_all_db(db) + shops = JSON.parse RestClient.get("http://pet-shop.api.mks.io/shops") + shops.shift + cats = shops.map do |shop| + shop_id = shop['id'] + JSON.parse RestClient.get("http://pet-shop.api.mks.io/shops/#{shop_id}/cats") + end + + dogs = shops.map do |shop| + shop_id = shop['id'] + JSON.parse RestClient.get("http://pet-shop.api.mks.io/shops/#{shop_id}/dogs") + end + + save_shops_to_db(shops,db) + save_cats_to_db(cats.flatten,db) + save_dogs_to_db(dogs.flatten,db) + end + + def self.save_shops_to_db(shops,db) + sq = [] + # "(1, 'Jo's Palace'), (2, Nicks Shop)" + shops.each do |shop| + shop["name"].gsub(/'/, "///'") + end + values = shops.map do |shop| + id = shop['id'].to_i + name = shop['name'] + db.escape_string(name.to_s) + ["(#{id}, '#{name}')"] + end + values = values.flatten.join(',') + sql = %Q[ + insert into shops (id, name) values #{values} + ] + + db.exec(sql) + end + + def self.save_cats_to_db(cats,db) + sql = %q[ + INSERT INTO cats (shopId, name, imageUrl, adopted) + VALUES ($1, $2, $3, $4) + ] + cats.each do |cat| + name = cat['name'] + imageurl = cat['imageUrl'] + adopted = cat['adopted'] + shopid = cat['shopId'] + + db.exec(sql, [shopid, name, imageurl, adopted]) + end + end + + def self.save_dogs_to_db(dogs,db) + sql = %q[ + INSERT INTO dogs (shopId, name, imageUrl, happiness, adopted) + VALUES ($1, $2, $3, $4, $5) + ] + dogs.each do |dog| + name = dog['name'] + imageurl = dog['imageUrl'] + happiness = dog['happiness'].to_i + x ||= dog['adopted'] + shopid = dog['shopId'] + + db.exec(sql, [shopid, name, imageurl, happiness, adopted]) + end + end + + def self.boolean_type_cast object, key + if object[key] == 't' + object[key] = true + elsif object[key] == 'f' + object[key] = false + end + object + end + +end diff --git a/lib/petshopserver/repos/cats_repo.rb b/lib/petshopserver/repos/cats_repo.rb new file mode 100644 index 00000000..e77b88ad --- /dev/null +++ b/lib/petshopserver/repos/cats_repo.rb @@ -0,0 +1,42 @@ +module Petshopserver + class CatsRepo + + def self.all db + sql = %q[SELECT * FROM cats] + result = db.exec(sql) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + end + + def self.all_by_shop db, id + sql = %q[SELECT * FROM cats WHERE "shopId" = $1] + result = db.exec(sql, [id]) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + + end + + def self.find db, id + sql = %q[SELECT * FROM cats WHERE id = $1] + result = db.exec(sql, [id]) + Petshopserver.boolean_type_cast(result.first, 'adopted') + #added class + end + + def self.save db, cat_data + if cat_data["id"] + id = cat_data["id"].to_i + sql = %q[UPDATE cats SET adopted = $1 WHERE id = $2 RETURNING *] + result = db.exec(sql, [cat_data["adopted"], cat_data["id"]]) + else + sql = %q[INSERT INTO cats ("shopId", name, "imageUrl", adopted) VALUES ($1,$2,$3,$4) RETURNING *] + result = db.exec(sql, [cat_data["shopId"], cat_data["name"], cat_data["imageUrl"],cat_data["adopted"]]) + end + result.first + end + + def self.destroy db, id + sql = %q[DELETE FROM cats WHERE id = $1] + db.exec(sql, [id]) + end + + end +end \ No newline at end of file diff --git a/lib/petshopserver/repos/dogs_repo.rb b/lib/petshopserver/repos/dogs_repo.rb new file mode 100644 index 00000000..7827e357 --- /dev/null +++ b/lib/petshopserver/repos/dogs_repo.rb @@ -0,0 +1,40 @@ +module Petshopserver + class DogsRepo + + def self.all db + sql = %q[SELECT * FROM dogs] + result = db.exec(sql) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + end + + def self.all_by_shop db, id + sql = %q[SELECT * FROM dogs WHERE "shopId" = $1] + result = db.exec(sql, [id]) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + end + + def self.find db, id + sql = %q[SELECT * FROM dogs WHERE id = $1] + result = db.exec(sql, [id]) + Petshopserver.boolean_type_cast(result.first, 'adopted') + end + + def self.save db, dog_data + if dog_data["id"] + id = dog_data["id"].to_i + sql = %q[UPDATE dogs SET adopted = $1 WHERE id = $2 RETURNING *] + result = db.exec(sql, [dog_data["adopted"], dog_data["id"]]) + else + sql = %q[INSERT INTO dogs ("shopId", name, "imageUrl", happiness, adopted) VALUES ($1,$2,$3,$4, $5) RETURNING *] + result = db.exec(sql, [dog_data["shopId"], dog_data["name"], dog_data["imageUrl"],dog_data["happiness"], dog_data["adopted"]]) + end + result.first + end + + def self.destroy db, id + sql = %q[DELETE FROM dogs WHERE id = $1] + db.exec(sql, [id]) + end + + end +end \ No newline at end of file diff --git a/lib/petshopserver/repos/shops_repo.rb b/lib/petshopserver/repos/shops_repo.rb new file mode 100644 index 00000000..7d14439d --- /dev/null +++ b/lib/petshopserver/repos/shops_repo.rb @@ -0,0 +1,37 @@ +module Petshopserver + + class ShopsRepo + # find a user by user ID. Intended to be used when + # someone is already authenticated. We keep their + # user id in the cookie. + + def self.find db, shop_id + sql = %q[SELECT * FROM shops WHERE id = $1] + result = db.exec(sql, [shop_id]) + result.first + end + + def self.find_by_name db, name + sql = %q[SELECT * FROM shops WHERE name = $1] + result = db.exec(sql, [name]) + result.first + end + + def self.all(db) + db.exec("SELECT * FROM shops").to_a + end + + # save shop info + def self.save db, shop_data + sql = %q[INSERT INTO shops (name) VALUES ($1) RETURNING *] + result = db.exec(sql, [shop_data[:name]]) + result.first + end + + def self.destroy db, id + sql = %q[DELETE FROM shops WHERE id = $1] + db.exec(sql, [id]) + end + + end +end diff --git a/lib/petshopserver/repos/users_repo.rb b/lib/petshopserver/repos/users_repo.rb new file mode 100644 index 00000000..c48397ad --- /dev/null +++ b/lib/petshopserver/repos/users_repo.rb @@ -0,0 +1,58 @@ +module Petshopserver + + class UsersRepo + # find a user by user ID. Intended to be used when + # someone is already authenticated. We keep their + # user id in the cookie. + def self.find db, user_id + sql = %q[SELECT * FROM users WHERE id = $1] + result = db.exec(sql, [user_id]) + result.first + end + + # find user by username. Intended to be used when + # someone tries to sign in. + def self.find_by_username db, username + sql = %q[SELECT * FROM users WHERE username = $1] + result = db.exec(sql, [username]) + result.first + end + + # when someone signs up use this method to save their + # information in the db. we're not encrypting passwords. + 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.adopt_cat db, user_id, cat_id + sql = %q[INSERT INTO userPets (user_id, cat_id) VALUES ($1, $2) RETURNING *] + sql1 = %q[UPDATE cats SET adopted = $1 WHERE id = $2 RETURNING *] + db.exec(sql1, [true, cat_id]) + result = db.exec(sql, [user_id, cat_id]) + result.first + end + + def self.find_all_cats_by_user_id db, user_id + sql = %q[SELECT c.id, c."shopId", c.name, c."imageUrl", c.adopted FROM userPets u INNER JOIN cats c ON c.id = u.cat_id WHERE user_id = $1] + result = db.exec(sql, [user_id]) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + end + + def self.adopt_dog db, user_id, dog_id + sql = %q[INSERT INTO userPets (user_id, dog_id) VALUES ($1, $2) RETURNING *] + sql1 = %q[UPDATE dogs SET adopted = $1 WHERE id = $2 RETURNING *] + db.exec(sql1, [true, dog_id]) + result = db.exec(sql, [user_id, dog_id]) + result.first + end + + def self.find_all_dogs_by_user_id db, user_id + sql = %q[SELECT d.id, d."shopId", d.name, d."imageUrl", d.happiness, d.adopted FROM userPets u INNER JOIN dogs d ON d.id = u.dog_id WHERE user_id = $1] + result = db.exec(sql, [user_id]) + result.entries.map { |x| Petshopserver.boolean_type_cast(x, 'adopted') } + end + + end +end diff --git a/server.rb b/server.rb index 07df95f4..c6f9fd36 100644 --- a/server.rb +++ b/server.rb @@ -1,96 +1,116 @@ require 'sinatra' require 'sinatra/reloader' require 'rest-client' -require 'json' +require 'rack-flash' +require 'pry-byebug' +require 'json' +require 'pg' + + +require_relative 'lib/petshopserver.rb' + + configure do + set :bind, '0.0.0.0' + enable :sessions + use Rack::Flash + end + + helpers do + def mydb + db = Petshopserver.create_db_connection 'petshopserver' + end + end -# # -# This is our only html view... -# get '/' do - if session[:user_id] - # TODO: Grab user from database - @current_user = $sample_user + if session['user_id'] + user_id = session['user_id'] + @current_user = Petshopserver::UsersRepo.find mydb, user_id + cats = Petshopserver::UsersRepo.find_all_cats_by_user_id mydb, user_id + dogs = Petshopserver::UsersRepo.find_all_dogs_by_user_id mydb, user_id + @current_user['cats'] = cats + @current_user['dogs'] = dogs + @current_user.to_json end erb :index end -# # -# ...the rest are JSON endpoints -# + +get '/logout' do + session.delete 'user_id' + redirect to '/' +end + get '/shops' do - headers['Content-Type'] = 'application/json' - RestClient.get("http://pet-shop.api.mks.io/shops") + shops = Petshopserver::ShopsRepo.all mydb + shops.to_json end post '/signin' do params = JSON.parse request.body.read - username = params['username'] password = params['password'] - - # TODO: Grab user by username from database and check password - user = { 'username' => 'alice', 'password' => '123' } - + user = Petshopserver::UsersRepo.find_by_username(mydb, username) + if password == user['password'] - headers['Content-Type'] = 'application/json' - # TODO: Return all pets adopted by this user - # TODO: Set session[:user_id] so the server will remember this user has logged in - $sample_user.to_json + # headers['Content-Type'] = 'application/json' + session['user_id'] = user['id'] + @current_user = Petshopserver::UsersRepo.find mydb, user['id'] + cats = Petshopserver::UsersRepo.find_all_cats_by_user_id mydb, user['id'] + dogs = Petshopserver::UsersRepo.find_all_dogs_by_user_id mydb, user['id'] + @current_user['cats'] = cats + @current_user['dogs'] = dogs + @current_user.to_json else status 401 end end - # # # # + +# # # # # Cats # # # # # + get '/shops/:id/cats' do - headers['Content-Type'] = 'application/json' id = params[:id] - # TODO: Grab from database instead - RestClient.get("http://pet-shop.api.mks.io/shops/#{id}/cats") + cats = Petshopserver::CatsRepo.all_by_shop mydb, id + cats.to_json end put '/shops/:shop_id/cats/:id/adopt' do - headers['Content-Type'] = 'application/json' shop_id = params[:shop_id] - id = params[:id] - # TODO: Grab from database instead - RestClient.put("http://pet-shop.api.mks.io/shops/#{shop_id}/cats/#{id}", - { adopted: true }, :content_type => 'application/json') - # TODO (after you create users table): Attach new cat to logged in user + cat_id = params[:id] + user_id = session['user_id'] + cat = Petshopserver::UsersRepo.adopt_cat mydb, user_id, cat_id + cat.to_json end - # # # # # Dogs # # # # # + get '/shops/:id/dogs' do - headers['Content-Type'] = 'application/json' id = params[:id] - # TODO: Update database instead - RestClient.get("http://pet-shop.api.mks.io/shops/#{id}/dogs") + dogs = Petshopserver::DogsRepo.all_by_shop mydb, id + dogs.to_json end put '/shops/:shop_id/dogs/:id/adopt' do - headers['Content-Type'] = 'application/json' shop_id = params[:shop_id] - id = params[:id] - # TODO: Update database instead - RestClient.put("http://pet-shop.api.mks.io/shops/#{shop_id}/dogs/#{id}", - { adopted: true }, :content_type => 'application/json') - # TODO (after you create users table): Attach new dog to logged in user + dog_id = params[:id] + user_id = session['user_id'] + dog = Petshopserver::UsersRepo.adopt_dog mydb, user_id, dog_id + dog.to_json end -$sample_user = { - id: 999, - username: 'alice', - cats: [ - { shopId: 1, name: "NaN Cat", imageUrl: "http://i.imgur.com/TOEskNX.jpg", adopted: true, id: 44 }, - { shopId: 8, name: "Meowzer", imageUrl: "http://www.randomkittengenerator.com/images/cats/rotator.php", id: 8, adopted: "true" } - ], - dogs: [ - { shopId: 1, name: "Leaf Pup", imageUrl: "http://i.imgur.com/kuSHji2.jpg", happiness: 2, id: 2, adopted: "true" } - ] -} +# $sample_user = { +# id: 999, +# username: 'alice', +# cats: [ +# { shopId: 1, name: "NaN Cat", imageUrl: "http://i.imgur.com/TOEskNX.jpg", adopted: true, id: 44 }, +# { shopId: 8, name: "Meowzer", imageUrl: "http://www.randomkittengenerator.com/images/cats/rotator.php", id: 8, adopted: "true" } +# ], +# dogs: [ +# { shopId: 1, name: "Leaf Pup", imageUrl: "http://i.imgur.com/kuSHji2.jpg", happiness: 2, id: 2, adopted: "true" } +# ] +# } diff --git a/spec/cats_repo_spec.rb b/spec/cats_repo_spec.rb new file mode 100644 index 00000000..51f5efc4 --- /dev/null +++ b/spec/cats_repo_spec.rb @@ -0,0 +1,2 @@ +require 'spec_helper' + diff --git a/spec/dogs_repo_spec.rb b/spec/dogs_repo_spec.rb new file mode 100644 index 00000000..335cafc3 --- /dev/null +++ b/spec/dogs_repo_spec.rb @@ -0,0 +1 @@ +require 'spec_helper' \ No newline at end of file diff --git a/spec/petshopserver_spec.rb b/spec/petshopserver_spec.rb new file mode 100644 index 00000000..335cafc3 --- /dev/null +++ b/spec/petshopserver_spec.rb @@ -0,0 +1 @@ +require 'spec_helper' \ No newline at end of file diff --git a/spec/server_spec.rb b/spec/server_spec.rb new file mode 100644 index 00000000..335cafc3 --- /dev/null +++ b/spec/server_spec.rb @@ -0,0 +1 @@ +require 'spec_helper' \ No newline at end of file diff --git a/spec/shops_repo_spec.rb b/spec/shops_repo_spec.rb new file mode 100644 index 00000000..335cafc3 --- /dev/null +++ b/spec/shops_repo_spec.rb @@ -0,0 +1 @@ +require 'spec_helper' \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 00000000..96f3ee41 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,14 @@ +# require 'server' +require './lib/petshopserver' # ? + +RSpec.configure do |config| + config.treat_symbols_as_metadata_keys_with_true_values = true + config.run_all_when_everything_filtered = true + config.filter_run :focus + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = 'random' +end \ No newline at end of file diff --git a/spec/users_repo_spec.rb b/spec/users_repo_spec.rb new file mode 100644 index 00000000..f8ec3695 --- /dev/null +++ b/spec/users_repo_spec.rb @@ -0,0 +1 @@ +require 'spec_helper'