Skip to content

Commit

Permalink
Initial import
Browse files Browse the repository at this point in the history
  • Loading branch information
danlucraft committed May 24, 2009
0 parents commit bcffad4
Show file tree
Hide file tree
Showing 17 changed files with 1,048 additions and 0 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Original file line 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
125 changes: 125 additions & 0 deletions app.rb
Original file line number Original file line 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






180 changes: 180 additions & 0 deletions domain.rb
Original file line number Original file line 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









48 changes: 48 additions & 0 deletions login-signup.rb
Original file line number Original file line 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
Loading

0 comments on commit bcffad4

Please sign in to comment.