-
Notifications
You must be signed in to change notification settings - Fork 40
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bcffad4
Showing
17 changed files
with
1,048 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
Retwis-RB | ||
========= | ||
|
||
An example application using the Redis key-value database. | ||
|
||
Daniel Lucraft (dan@fluentradical.com) | ||
|
||
Requirements | ||
------------ | ||
|
||
* Ruby | ||
* Sinatra: sudo gem install sinatra | ||
* Redis: http://code.google.com/p/redis/ | ||
|
||
Starting Application | ||
-------------------- | ||
|
||
Make sure the redis server is running. | ||
Run: | ||
|
||
ruby app.rb | ||
|
||
License | ||
------- | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
|
||
require 'rubygems' | ||
require 'sinatra' | ||
require 'erb' | ||
require 'redis' | ||
|
||
require 'domain' | ||
require 'login-signup' | ||
|
||
set :sessions, true | ||
|
||
def redis | ||
$redis ||= Redis.new | ||
end | ||
|
||
# redis.flush_db | ||
|
||
before do | ||
keys = redis.keys("*") | ||
p keys | ||
end | ||
|
||
get '/' do | ||
@posts = @logged_in_user.timeline | ||
erb :index | ||
end | ||
|
||
get '/timeline' do | ||
@posts = Timeline.page(1) | ||
erb :timeline | ||
end | ||
|
||
post '/post' do | ||
if params[:content].length == 0 | ||
@posting_error = "You didn't enter anything." | ||
elsif params[:content].length > 140 | ||
@posting_error = "Keep it to 140 characters please!" | ||
end | ||
if @posting_error | ||
@posts = @logged_in_user.timeline | ||
erb :index | ||
else | ||
Post.create(@logged_in_user, params[:content]) | ||
redirect '/' | ||
end | ||
end | ||
|
||
get '/:follower/follow/:followee' do |follower_username, followee_username| | ||
follower = User.find_by_username(follower_username) | ||
followee = User.find_by_username(followee_username) | ||
follower.follow(followee) | ||
redirect "/" + followee_username | ||
end | ||
|
||
get '/:follower/stopfollow/:followee' do |follower_username, followee_username| | ||
follower = User.find_by_username(follower_username) | ||
followee = User.find_by_username(followee_username) | ||
follower.stop_following(followee) | ||
redirect "/" + followee_username | ||
end | ||
|
||
get '/:username' do |username| | ||
@user = User.find_by_username(username) | ||
|
||
@posts = @user.posts | ||
@followers = @user.followers | ||
@followees = @user.followees | ||
erb :profile | ||
end | ||
|
||
|
||
helpers do | ||
def link_to_user(user) | ||
f = <<-HTML | ||
<a href="/#{user.username}">#{user.username}</a> | ||
HTML | ||
end | ||
|
||
def pluralize(singular, plural, count) | ||
if count == 1 | ||
count.to_s + " " + singular | ||
else | ||
count.to_s + " " + plural | ||
end | ||
end | ||
|
||
def time_ago_in_words(time) | ||
distance_in_seconds = (Time.now - time).round | ||
case distance_in_seconds | ||
when 0..10 | ||
return "just now" | ||
when 10..60 | ||
return "less than a minute ago" | ||
end | ||
distance_in_minutes = (distance_in_seconds/60).round | ||
case distance_in_minutes | ||
when 0..1 | ||
return "a minute ago" | ||
when 2..45 | ||
return distance_in_minutes.round.to_s + " minutes ago" | ||
when 46..89 | ||
return "about an hour ago" | ||
when 90..1439 | ||
return (distance_in_minutes/60).round.to_s + " hours ago" | ||
when 1440..2879 | ||
return "about a day ago" | ||
when 2880..43199 | ||
(distance_in_minutes / 1440).round.to_s + " days ago" | ||
when 43200..86399 | ||
"about a month ago" | ||
when 86400..525599 | ||
(distance_in_minutes / 43200).round.to_s + " months ago" | ||
when 525600..1051199 | ||
"about a year ago" | ||
else | ||
"over " + (distance_in_minutes / 525600).round.to_s + " years ago" | ||
end | ||
end | ||
end | ||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
|
||
require 'redis' | ||
|
||
class Timeline | ||
def self.page(page) | ||
from = (page-1)*10 | ||
to = (page)*10 | ||
post_ids = redis.list_range("timeline", from, to) | ||
post_ids.map {|post_id| Post.new(post_id)} | ||
end | ||
end | ||
|
||
class Model | ||
def initialize(id) | ||
@id = id | ||
end | ||
|
||
def ==(other) | ||
@id.to_s == other.id.to_s | ||
end | ||
|
||
attr_reader :id | ||
|
||
def self.property(name) | ||
klass = self.name.downcase | ||
self.class_eval <<-RUBY | ||
def #{name} | ||
_#{name} | ||
end | ||
def _#{name} | ||
redis.get("#{klass}:id:" + id.to_s + ":#{name}") | ||
end | ||
def #{name}=(val) | ||
redis.set("#{klass}:id:" + id.to_s + ":#{name}", val) | ||
end | ||
RUBY | ||
end | ||
end | ||
|
||
class User < Model | ||
def self.find_by_username(username) | ||
if id = redis.get("user:username:#{username}") | ||
User.new(id) | ||
end | ||
end | ||
|
||
def self.find_by_id(id) | ||
if redis.key?("user:id:#{id}:username") | ||
User.new(id) | ||
end | ||
end | ||
|
||
def self.create(username, password) | ||
user_id = redis.incr("user:uid") | ||
salt = User.new_salt | ||
redis.set("user:id:#{user_id}:username", username) | ||
redis.set("user:username:#{username}", user_id) | ||
redis.set("user:id:#{user_id}:salt", salt) | ||
redis.set("user:id:#{user_id}:hashed_password", hash_pw(salt, password)) | ||
redis.push_head("users", user_id) | ||
User.new(user_id) | ||
end | ||
|
||
def self.new_users | ||
redis.list_range("users", 0, 10).map do |user_id| | ||
User.new(user_id) | ||
end | ||
end | ||
|
||
def self.new_salt | ||
arr = %w(a b c d e f) | ||
(0..6).to_a.map{ arr[rand(6)] }.join | ||
end | ||
|
||
def self.hash_pw(salt, password) | ||
Digest::MD5.hexdigest(salt + password) | ||
end | ||
|
||
property :username | ||
property :salt | ||
property :hashed_password | ||
|
||
def posts(page=1) | ||
from, to = (page-1)*10, page*10 | ||
redis.list_range("user:id:#{id}:posts", from, to).map do |post_id| | ||
Post.new(post_id) | ||
end | ||
end | ||
|
||
def timeline(page=1) | ||
from, to = (page-1)*10, page*10 | ||
redis.list_range("user:id:#{id}:timeline", from, to).map do |post_id| | ||
Post.new(post_id) | ||
end | ||
end | ||
|
||
def add_post(post) | ||
redis.push_head("user:id:#{id}:posts", post.id) | ||
redis.push_head("user:id:#{id}:timeline", post.id) | ||
end | ||
|
||
def add_timeline_post(post) | ||
redis.push_head("user:id:#{id}:timeline", post.id) | ||
end | ||
|
||
def follow(user) | ||
return if user == self | ||
redis.set_add("user:id:#{id}:followees", user.id) | ||
user.add_follower(self) | ||
end | ||
|
||
def stop_following(user) | ||
redis.set_delete("user:id:#{id}:followees", user.id) | ||
user.remove_follower(self) | ||
end | ||
|
||
def following?(user) | ||
redis.set_member?("user:id:#{id}:followees", user.id) | ||
end | ||
|
||
def followers | ||
redis.set_members("user:id:#{id}:followers").map do |user_id| | ||
User.new(user_id) | ||
end | ||
end | ||
|
||
def followees | ||
redis.set_members("user:id:#{id}:followees").map do |user_id| | ||
User.new(user_id) | ||
end | ||
end | ||
|
||
protected | ||
|
||
def add_follower(user) | ||
redis.set_add("user:id:#{id}:followers", user.id) | ||
end | ||
|
||
def remove_follower(user) | ||
redis.set_delete("user:id:#{id}:followers", user.id) | ||
end | ||
end | ||
|
||
class Post < Model | ||
def self.create(user, content) | ||
post_id = redis.incr("post:uid") | ||
post = Post.new(post_id) | ||
post.content = content | ||
post.user_id = user.id | ||
post.created_at = Time.now.to_s | ||
post.user.add_post(post) | ||
redis.push_head("timeline", post_id) | ||
post.user.followers.each do |follower| | ||
follower.add_timeline_post(post) | ||
end | ||
end | ||
|
||
property :content | ||
property :user_id | ||
property :created_at | ||
|
||
def created_at | ||
Time.parse(_created_at) | ||
end | ||
|
||
def user | ||
User.new(user_id) | ||
end | ||
end | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
|
||
before do | ||
unless %w(/login /signup).include?(request.path_info) or | ||
request.path_info =~ /\.css$/ or | ||
@logged_in_user = User.find_by_id(session["user_id"]) | ||
redirect '/login', 303 | ||
end | ||
puts "logged in as:#{@logged_in_user.username}" if @logged_in_user | ||
end | ||
|
||
get '/login' do | ||
erb :login | ||
end | ||
|
||
post '/login' do | ||
if user = User.find_by_username(params[:username]) and | ||
User.hash_pw(user.salt, params[:password]) == user.hashed_password | ||
session["user_id"] = user.id | ||
redirect '/' | ||
else | ||
@login_error = "Incorrect username or password" | ||
erb :login | ||
end | ||
end | ||
|
||
post '/signup' do | ||
if redis.key?("user:username:#{params[:username]}") | ||
@signup_error = "That username is taken." | ||
elsif params[:username].length < 4 | ||
@signup_error = "Username must be at least 4 characters" | ||
elsif params[:password].length < 6 | ||
@signup_error = "Password must be at least 6 characters!" | ||
elsif params[:password] != params[:password_confirmation] | ||
@signup_error = "Passwords do not match!" | ||
end | ||
if @signup_error | ||
erb :login | ||
else | ||
user = User.create(params[:username], params[:password]) | ||
session["user_id"] = user.id | ||
redirect "/" | ||
end | ||
end | ||
|
||
get '/logout' do | ||
session["user_id"] = nil | ||
redirect '/login' | ||
end |
Oops, something went wrong.