Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial import

  • Loading branch information...
commit bcffad497106c39576756889fbc1efdeb09e2f4b 0 parents
@danlucraft authored
26 README.md
@@ -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 app.rb
@@ -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 domain.rb
@@ -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 login-signup.rb
@@ -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
71 public/css/custom.css
@@ -0,0 +1,71 @@
+
+a {
+ text-decoration: none;
+ color: blue;
+}
+
+a:visited {
+ color: blue;
+}
+
+.right-align {
+ text-align: right;
+}
+
+body {
+ font-size: 100%;
+}
+
+.container {
+}
+
+#updateform {
+ background: #EEE;
+ margin: 10px;
+}
+
+.rightcolumn {
+
+}
+
+.box {
+ padding: 10px;
+ margin: 10px;
+ -moz-border-radius-bottomleft:5px;
+ -moz-border-radius-bottomright:5px;
+ -moz-border-radius-topleft:5px;
+ -moz-border-radius-topright:5px;
+ background: #EEE;
+}
+
+textarea {
+ height: 5em;
+ width: 500px;
+}
+
+#posts {
+ margin: 10px;
+ padding: 10px;
+}
+
+#posts .date {
+ font-size: 75%;
+ color: #999;
+}
+
+#posts hr {
+ margin: 0;
+}
+
+#posts .post {
+ border-top: 1px dashed #DDD;
+ padding-top: 10px;
+ padding-bottom: 10px;
+}
+
+.user-list li {
+ list-style: none;
+}
+
+
+
27 public/css/ie.css
@@ -0,0 +1,27 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 0.8
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* ie.css */
+body {text-align:center;}
+.container {text-align:left;}
+* html .column, * html div.span-1, * html div.span-2, * html div.span-3, * html div.span-4, * html div.span-5, * html div.span-6, * html div.span-7, * html div.span-8, * html div.span-9, * html div.span-10, * html div.span-11, * html div.span-12, * html div.span-13, * html div.span-14, * html div.span-15, * html div.span-16, * html div.span-17, * html div.span-18, * html div.span-19, * html div.span-20, * html div.span-21, * html div.span-22, * html div.span-23, * html div.span-24 {overflow-x:hidden;}
+* html legend {margin:0px -8px 16px 0;padding:0;}
+ol {margin-left:2em;}
+sup {vertical-align:text-top;}
+sub {vertical-align:text-bottom;}
+html>body p code {*white-space:normal;}
+hr {margin:-8px auto 11px;}
+img {-ms-interpolation-mode:bicubic;}
+.clearfix, .container {display:inline-block;}
+* html .clearfix, * html .container {height:1%;}
+fieldset {padding-top:0;}
30 public/css/print.css
@@ -0,0 +1,30 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 0.8
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* print.css */
+body {line-height:1.5;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;color:#000;background:none;font-size:10pt;}
+.container {background:none;}
+hr {background:#ccc;color:#ccc;width:100%;height:2px;margin:2em 0;padding:0;border:none;}
+hr.space {background:#fff;color:#fff;}
+h1, h2, h3, h4, h5, h6 {font-family:"Helvetica Neue", Arial, "Lucida Grande", sans-serif;}
+code {font:.9em "Courier New", Monaco, Courier, monospace;}
+img {float:left;margin:1.5em 1.5em 1.5em 0;}
+a img {border:none;}
+p img.top {margin-top:0;}
+blockquote {margin:1.5em;padding:1em;font-style:italic;font-size:.9em;}
+.small {font-size:.9em;}
+.large {font-size:1.1em;}
+.quiet {color:#999;}
+.hide {display:none;}
+a:link, a:visited {background:transparent;font-weight:700;text-decoration:underline;}
+a:link:after, a:visited:after {content:" (" attr(href) ")";font-size:90%;}
252 public/css/screen.css
@@ -0,0 +1,252 @@
+/* -----------------------------------------------------------------------
+
+
+ Blueprint CSS Framework 0.8
+ http://blueprintcss.org
+
+ * Copyright (c) 2007-Present. See LICENSE for more info.
+ * See README for instructions on how to use Blueprint.
+ * For credits and origins, see AUTHORS.
+ * This is a compressed file. See the sources in the 'src' directory.
+
+----------------------------------------------------------------------- */
+
+/* reset.css */
+html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, code, del, dfn, em, img, q, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td {margin:0;padding:0;border:0;font-weight:inherit;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}
+body {line-height:1.5;}
+table {border-collapse:separate;border-spacing:0;}
+caption, th, td {text-align:left;font-weight:normal;}
+table, td, th {vertical-align:middle;}
+blockquote:before, blockquote:after, q:before, q:after {content:"";}
+blockquote, q {quotes:"" "";}
+a img {border:none;}
+
+/* typography.css */
+body {font-size:75%;color:#222;background:#fff;font-family:"Helvetica Neue", Arial, Helvetica, sans-serif;}
+h1, h2, h3, h4, h5, h6 {font-weight:normal;color:#111;}
+h1 {font-size:3em;line-height:1;margin-bottom:0.5em;}
+h2 {font-size:2em;margin-bottom:0.75em;}
+h3 {font-size:1.5em;line-height:1;margin-bottom:1em;}
+h4 {font-size:1.2em;line-height:1.25;margin-bottom:1.25em;}
+h5 {font-size:1em;font-weight:bold;margin-bottom:1.5em;}
+h6 {font-size:1em;font-weight:bold;}
+h1 img, h2 img, h3 img, h4 img, h5 img, h6 img {margin:0;}
+p {margin:0 0 1.5em;}
+p img.left {float:left;margin:1.5em 1.5em 1.5em 0;padding:0;}
+p img.right {float:right;margin:1.5em 0 1.5em 1.5em;}
+a:focus, a:hover {color:#000;}
+a {color:#009;text-decoration:underline;}
+blockquote {margin:1.5em;color:#666;font-style:italic;}
+strong {font-weight:bold;}
+em, dfn {font-style:italic;}
+dfn {font-weight:bold;}
+sup, sub {line-height:0;}
+abbr, acronym {border-bottom:1px dotted #666;}
+address {margin:0 0 1.5em;font-style:italic;}
+del {color:#666;}
+pre {margin:1.5em 0;white-space:pre;}
+pre, code, tt {font:1em 'andale mono', 'lucida console', monospace;line-height:1.5;}
+li ul, li ol {margin:0 1.5em;}
+ul, ol {margin:0 1.5em 1.5em 1.5em;}
+ul {list-style-type:disc;}
+ol {list-style-type:decimal;}
+dl {margin:0 0 1.5em 0;}
+dl dt {font-weight:bold;}
+dd {margin-left:1.5em;}
+table {margin-bottom:1.4em;width:100%;}
+th {font-weight:bold;}
+thead th {background:#c3d9ff;}
+th, td, caption {padding:4px 10px 4px 5px;}
+tr.even td {background:#e5ecf9;}
+tfoot {font-style:italic;}
+caption {background:#eee;}
+.small {font-size:.8em;margin-bottom:1.875em;line-height:1.875em;}
+.large {font-size:1.2em;line-height:2.5em;margin-bottom:1.25em;}
+.hide {display:none;}
+.quiet {color:#666;}
+.loud {color:#000;}
+.highlight {background:#ff0;}
+.added {background:#060;color:#fff;}
+.removed {background:#900;color:#fff;}
+.first {margin-left:0;padding-left:0;}
+.last {margin-right:0;padding-right:0;}
+.top {margin-top:0;padding-top:0;}
+.bottom {margin-bottom:0;padding-bottom:0;}
+
+/* forms.css */
+label {font-weight:bold;}
+fieldset {padding:1.4em;margin:0 0 1.5em 0;border:1px solid #ccc;}
+legend {font-weight:bold;font-size:1.2em;}
+input.text, input.title, textarea, select {margin:0.5em 0;border:1px solid #bbb;}
+input.text:focus, input.title:focus, textarea:focus, select:focus {border:1px solid #666;}
+input.text, input.title {width:300px;padding:5px;}
+input.title {font-size:1.5em;}
+textarea {width:390px;height:250px;padding:5px;}
+.error, .notice, .success {padding:.8em;margin-bottom:1em;border:2px solid #ddd;}
+.error {background:#FBE3E4;color:#8a1f11;border-color:#FBC2C4;}
+.notice {background:#FFF6BF;color:#514721;border-color:#FFD324;}
+.success {background:#E6EFC2;color:#264409;border-color:#C6D880;}
+.error a {color:#8a1f11;}
+.notice a {color:#514721;}
+.success a {color:#264409;}
+
+/* grid.css */
+.container {width:950px;margin:0 auto;}
+.showgrid {background:url(src/grid.png);}
+.column, div.span-1, div.span-2, div.span-3, div.span-4, div.span-5, div.span-6, div.span-7, div.span-8, div.span-9, div.span-10, div.span-11, div.span-12, div.span-13, div.span-14, div.span-15, div.span-16, div.span-17, div.span-18, div.span-19, div.span-20, div.span-21, div.span-22, div.span-23, div.span-24 {float:left;margin-right:10px;}
+.last, div.last {margin-right:0;}
+.span-1 {width:30px;}
+.span-2 {width:70px;}
+.span-3 {width:110px;}
+.span-4 {width:150px;}
+.span-5 {width:190px;}
+.span-6 {width:230px;}
+.span-7 {width:270px;}
+.span-8 {width:310px;}
+.span-9 {width:350px;}
+.span-10 {width:390px;}
+.span-11 {width:430px;}
+.span-12 {width:470px;}
+.span-13 {width:510px;}
+.span-14 {width:550px;}
+.span-15 {width:590px;}
+.span-16 {width:630px;}
+.span-17 {width:670px;}
+.span-18 {width:710px;}
+.span-19 {width:750px;}
+.span-20 {width:790px;}
+.span-21 {width:830px;}
+.span-22 {width:870px;}
+.span-23 {width:910px;}
+.span-24, div.span-24 {width:950px;margin:0;}
+input.span-1, textarea.span-1, input.span-2, textarea.span-2, input.span-3, textarea.span-3, input.span-4, textarea.span-4, input.span-5, textarea.span-5, input.span-6, textarea.span-6, input.span-7, textarea.span-7, input.span-8, textarea.span-8, input.span-9, textarea.span-9, input.span-10, textarea.span-10, input.span-11, textarea.span-11, input.span-12, textarea.span-12, input.span-13, textarea.span-13, input.span-14, textarea.span-14, input.span-15, textarea.span-15, input.span-16, textarea.span-16, input.span-17, textarea.span-17, input.span-18, textarea.span-18, input.span-19, textarea.span-19, input.span-20, textarea.span-20, input.span-21, textarea.span-21, input.span-22, textarea.span-22, input.span-23, textarea.span-23, input.span-24, textarea.span-24 {border-left-width:1px!important;border-right-width:1px!important;padding-left:5px!important;padding-right:5px!important;}
+input.span-1, textarea.span-1 {width:18px!important;}
+input.span-2, textarea.span-2 {width:58px!important;}
+input.span-3, textarea.span-3 {width:98px!important;}
+input.span-4, textarea.span-4 {width:138px!important;}
+input.span-5, textarea.span-5 {width:178px!important;}
+input.span-6, textarea.span-6 {width:218px!important;}
+input.span-7, textarea.span-7 {width:258px!important;}
+input.span-8, textarea.span-8 {width:298px!important;}
+input.span-9, textarea.span-9 {width:338px!important;}
+input.span-10, textarea.span-10 {width:378px!important;}
+input.span-11, textarea.span-11 {width:418px!important;}
+input.span-12, textarea.span-12 {width:458px!important;}
+input.span-13, textarea.span-13 {width:498px!important;}
+input.span-14, textarea.span-14 {width:538px!important;}
+input.span-15, textarea.span-15 {width:578px!important;}
+input.span-16, textarea.span-16 {width:618px!important;}
+input.span-17, textarea.span-17 {width:658px!important;}
+input.span-18, textarea.span-18 {width:698px!important;}
+input.span-19, textarea.span-19 {width:738px!important;}
+input.span-20, textarea.span-20 {width:778px!important;}
+input.span-21, textarea.span-21 {width:818px!important;}
+input.span-22, textarea.span-22 {width:858px!important;}
+input.span-23, textarea.span-23 {width:898px!important;}
+input.span-24, textarea.span-24 {width:938px!important;}
+.append-1 {padding-right:40px;}
+.append-2 {padding-right:80px;}
+.append-3 {padding-right:120px;}
+.append-4 {padding-right:160px;}
+.append-5 {padding-right:200px;}
+.append-6 {padding-right:240px;}
+.append-7 {padding-right:280px;}
+.append-8 {padding-right:320px;}
+.append-9 {padding-right:360px;}
+.append-10 {padding-right:400px;}
+.append-11 {padding-right:440px;}
+.append-12 {padding-right:480px;}
+.append-13 {padding-right:520px;}
+.append-14 {padding-right:560px;}
+.append-15 {padding-right:600px;}
+.append-16 {padding-right:640px;}
+.append-17 {padding-right:680px;}
+.append-18 {padding-right:720px;}
+.append-19 {padding-right:760px;}
+.append-20 {padding-right:800px;}
+.append-21 {padding-right:840px;}
+.append-22 {padding-right:880px;}
+.append-23 {padding-right:920px;}
+.prepend-1 {padding-left:40px;}
+.prepend-2 {padding-left:80px;}
+.prepend-3 {padding-left:120px;}
+.prepend-4 {padding-left:160px;}
+.prepend-5 {padding-left:200px;}
+.prepend-6 {padding-left:240px;}
+.prepend-7 {padding-left:280px;}
+.prepend-8 {padding-left:320px;}
+.prepend-9 {padding-left:360px;}
+.prepend-10 {padding-left:400px;}
+.prepend-11 {padding-left:440px;}
+.prepend-12 {padding-left:480px;}
+.prepend-13 {padding-left:520px;}
+.prepend-14 {padding-left:560px;}
+.prepend-15 {padding-left:600px;}
+.prepend-16 {padding-left:640px;}
+.prepend-17 {padding-left:680px;}
+.prepend-18 {padding-left:720px;}
+.prepend-19 {padding-left:760px;}
+.prepend-20 {padding-left:800px;}
+.prepend-21 {padding-left:840px;}
+.prepend-22 {padding-left:880px;}
+.prepend-23 {padding-left:920px;}
+div.border {padding-right:4px;margin-right:5px;border-right:1px solid #eee;}
+div.colborder {padding-right:24px;margin-right:25px;border-right:1px solid #eee;}
+.pull-1 {margin-left:-40px;}
+.pull-2 {margin-left:-80px;}
+.pull-3 {margin-left:-120px;}
+.pull-4 {margin-left:-160px;}
+.pull-5 {margin-left:-200px;}
+.pull-6 {margin-left:-240px;}
+.pull-7 {margin-left:-280px;}
+.pull-8 {margin-left:-320px;}
+.pull-9 {margin-left:-360px;}
+.pull-10 {margin-left:-400px;}
+.pull-11 {margin-left:-440px;}
+.pull-12 {margin-left:-480px;}
+.pull-13 {margin-left:-520px;}
+.pull-14 {margin-left:-560px;}
+.pull-15 {margin-left:-600px;}
+.pull-16 {margin-left:-640px;}
+.pull-17 {margin-left:-680px;}
+.pull-18 {margin-left:-720px;}
+.pull-19 {margin-left:-760px;}
+.pull-20 {margin-left:-800px;}
+.pull-21 {margin-left:-840px;}
+.pull-22 {margin-left:-880px;}
+.pull-23 {margin-left:-920px;}
+.pull-24 {margin-left:-960px;}
+.pull-1, .pull-2, .pull-3, .pull-4, .pull-5, .pull-6, .pull-7, .pull-8, .pull-9, .pull-10, .pull-11, .pull-12, .pull-13, .pull-14, .pull-15, .pull-16, .pull-17, .pull-18, .pull-19, .pull-20, .pull-21, .pull-22, .pull-23, .pull-24 {float:left;position:relative;}
+.push-1 {margin:0 -40px 1.5em 40px;}
+.push-2 {margin:0 -80px 1.5em 80px;}
+.push-3 {margin:0 -120px 1.5em 120px;}
+.push-4 {margin:0 -160px 1.5em 160px;}
+.push-5 {margin:0 -200px 1.5em 200px;}
+.push-6 {margin:0 -240px 1.5em 240px;}
+.push-7 {margin:0 -280px 1.5em 280px;}
+.push-8 {margin:0 -320px 1.5em 320px;}
+.push-9 {margin:0 -360px 1.5em 360px;}
+.push-10 {margin:0 -400px 1.5em 400px;}
+.push-11 {margin:0 -440px 1.5em 440px;}
+.push-12 {margin:0 -480px 1.5em 480px;}
+.push-13 {margin:0 -520px 1.5em 520px;}
+.push-14 {margin:0 -560px 1.5em 560px;}
+.push-15 {margin:0 -600px 1.5em 600px;}
+.push-16 {margin:0 -640px 1.5em 640px;}
+.push-17 {margin:0 -680px 1.5em 680px;}
+.push-18 {margin:0 -720px 1.5em 720px;}
+.push-19 {margin:0 -760px 1.5em 760px;}
+.push-20 {margin:0 -800px 1.5em 800px;}
+.push-21 {margin:0 -840px 1.5em 840px;}
+.push-22 {margin:0 -880px 1.5em 880px;}
+.push-23 {margin:0 -920px 1.5em 920px;}
+.push-24 {margin:0 -960px 1.5em 960px;}
+.push-1, .push-2, .push-3, .push-4, .push-5, .push-6, .push-7, .push-8, .push-9, .push-10, .push-11, .push-12, .push-13, .push-14, .push-15, .push-16, .push-17, .push-18, .push-19, .push-20, .push-21, .push-22, .push-23, .push-24 {float:right;position:relative;}
+.prepend-top {margin-top:1.5em;}
+.append-bottom {margin-bottom:1.5em;}
+.box {padding:1.5em;margin-bottom:1.5em;background:#E5ECF9;}
+hr {background:#ddd;color:#ddd;clear:both;float:none;width:100%;height:.1em;margin:0 0 1.45em;border:none;}
+hr.space {background:#fff;color:#fff;}
+.clearfix:after, .container:after {content:"\0020";display:block;height:0;clear:both;visibility:hidden;overflow:hidden;}
+.clearfix, .container {display:block;}
+.clear {clear:both;}
92 redis.conf
@@ -0,0 +1,92 @@
+# Redis configuration file example
+
+# By default Redis does not run as a daemon. Use 'yes' if you need it.
+# Note that Redis will write a pid file in /var/run/redis.pid when daemonized.
+daemonize no
+
+# When run as a daemon, Redis write a pid file in /var/run/redis.pid by default.
+# You can specify a custom pid file location here.
+pidfile /var/run/redis.pid
+
+# Accept connections on the specified port, default is 6379
+port 6379
+
+# If you want you can bind a single interface, if the bind option is not
+# specified all the interfaces will listen for connections.
+#
+# bind 127.0.0.1
+
+# Close the connection after a client is idle for N seconds (0 to disable)
+timeout 300
+
+# Save the DB on disk:
+#
+# save <seconds> <changes>
+#
+# Will save the DB if both the given number of seconds and the given
+# number of write operations against the DB occurred.
+#
+# In the example below the behaviour will be to save:
+# after 900 sec (15 min) if at least 1 key changed
+# after 300 sec (5 min) if at least 10 keys changed
+# after 60 sec if at least 10000 keys changed
+save 10 1
+save 5 10
+save 1 10000
+
+# The filename where to dump the DB
+dbfilename dump.rdb
+
+# For default save/load DB in/from the working directory
+# Note that you must specify a directory not a file name.
+dir ./
+
+# Set server verbosity to 'debug'
+# it can be one of:
+# debug (a lot of information, useful for development/testing)
+# notice (moderately verbose, what you want in production probably)
+# warning (only very important / critical messages are logged)
+loglevel debug
+
+# Specify the log file name. Also 'stdout' can be used to force
+# the demon to log on the standard output. Note that if you use standard
+# output for logging but daemonize, logs will be sent to /dev/null
+logfile stdout
+
+# Set the number of databases. The default database is DB 0, you can select
+# a different one on a per-connection basis using SELECT <dbid> where
+# dbid is a number between 0 and 'databases'-1
+databases 16
+
+################################# REPLICATION #################################
+
+# Master-Slave replication. Use slaveof to make a Redis instance a copy of
+# another Redis server. Note that the configuration is local to the slave
+# so for example it is possible to configure the slave to save the DB with a
+# different interval, or to listen to another port, and so on.
+
+# slaveof <masterip> <masterport>
+
+################################## SECURITY ###################################
+
+# Require clients to issue AUTH <PASSWORD> before processing any other
+# commands. This might be useful in environments in which you do not trust
+# others with access to the host running redis-server.
+#
+# This should stay commented out for backward compatibility and because most
+# people do not need auth (e.g. they run their own servers).
+
+# requirepass foobared
+
+############################### ADVANCED CONFIG ###############################
+
+# Glue small output buffers together in order to send small replies in a
+# single TCP packet. Uses a bit more CPU but most of the times it is a win
+# in terms of number of queries per second. Use 'yes' if unsure.
+glueoutputbuf yes
+
+# Use object sharing. Can save a lot of memory if you have many common
+# string in your dataset, but performs lookups against the shared objects
+# pool so it uses more CPU and can be a bit slower. Usually it's a good
+# idea.
+shareobjects no
12 views/_posts.erb
@@ -0,0 +1,12 @@
+
+<div id="posts" class="span-15">
+ <% @posts.each do |post| %>
+ <div class="post">
+ <strong><%= link_to_user(post.user) %></strong>
+ <%= post.content %><br />
+ <div class="date">
+ <%= time_ago_in_words(post.created_at) %>
+ </div>
+ </div>
+ <% end %>
+</div>
9 views/_user_list.erb
@@ -0,0 +1,9 @@
+
+<ul class="user-list">
+ <% users[0..99].each do |user| %>
+ <li><%= link_to_user(user) %></li>
+ <% end %>
+</ul>
+<% if users.length > 100 %>
+... and more!
+<% end %>
10 views/footer.erb
@@ -0,0 +1,10 @@
+
+ </div>
+ <div class="span-24 last">
+ <hr />
+ Retwis-RB, based on Redis database by antirez. Ported from PHP to Ruby by danlucraft. Sinatra powered.
+ See source code on github.
+ </div>
+ </div>
+ </body>
+</html>
29 views/header.erb
@@ -0,0 +1,29 @@
+<ntml>
+ <head>
+ <title>Retwis-RB</title>
+ <link rel="stylesheet" href="/css/screen.css" type="text/css" media="screen, projection">
+ <link rel="stylesheet" href="/css/print.css" type="text/css" media="print">
+ <!--[if IE]>
+ <link rel="stylesheet" href="/css/ie.css" type="text/css" media="screen, projection">
+ <![endif]-->
+ <link rel="stylesheet" href="/css/custom.css" type="text/css" media="screen, projection">
+ </head>
+ <body>
+ <br />
+ <div class="container">
+ <div id="header" class="span-24">
+ <div class="span-12">
+ <h1>Retwis-RB</h1>
+ </div>
+ <div class="span-12 last right-align">
+ <% if @logged_in_user %>
+ <br /><br />
+ <a href="/">home</a> |
+ <%= link_to_user(@logged_in_user) %> |
+ <a href="/timeline">timeline</a> |
+ <a href="/logout">logout</a>
+ <% end %>
+ </div>
+ <hr />
+ </div>
+ <div class="span-24">
35 views/index.erb
@@ -0,0 +1,35 @@
+
+<%= erb :header %>
+
+<div class="span-16" id="maincol">
+ <div id="updateform" class="box">
+ <% if @posting_error %>
+ <div class="error">
+ <%= @posting_error %>
+ </div>
+ <% end %>
+ <form action="/post" method="post">
+ <%= @logged_in_user.username %>, what's on your mind? <br />
+ <textarea name="content" rows="3" columns="70"></textarea><br />
+ <input type="submit" value="Update">
+ </form>
+ </div>
+ <%= erb :_posts %>
+
+</div>
+
+<div class="span-7 last" id="rightcol">
+ <div class="span-7 box">
+ <% followers = @logged_in_user.followers %>
+ <h4><%= pluralize("follower", "followers", followers.length) %></h4>
+ <%= erb :_user_list, :locals => {:users => followers } %>
+ </div>
+ <div class="span-7 box">
+ <% followees = @logged_in_user.followees %>
+ <h4>Following <%= followees.length %></h4>
+ <%= erb :_user_list, :locals => {:users => followees } %>
+ </div>
+</div>
+
+
+<%= erb :footer %>
56 views/login.erb
@@ -0,0 +1,56 @@
+<%= erb :header %>
+
+<div class="span-11 box">
+ <h2>Login</h2>
+
+ <% if @login_error %>
+ <div class="error">
+ <%= @login_error %>
+ </div>
+ <% end %>
+
+ <form action="login" method="post">
+ <table>
+ <tr>
+ <td>username</td>
+ <td><input name="username" /></td>
+ </tr>
+ <tr>
+ <td>password</td>
+ <td><input type="password" name="password" /></td>
+ </tr>
+ </table>
+ <input type="submit" value="Log in" />
+ </form>
+</div>
+
+<div class="span-11 last box">
+ <h2>Sign up</h2>
+
+ <% if @signup_error %>
+ <div class="error">
+ <%= @signup_error %>
+ </div>
+ <% end %>
+
+ <form action="signup" method="post">
+ <table>
+ <tr>
+ <td>username</td>
+ <td><input name="username" value="<%= params[:username] %>"/></td>
+ </tr>
+ <tr>
+ <td>password</td>
+ <td><input type="password" name="password" /></td>
+ </tr>
+ <tr>
+ <td>password again</td>
+ <td><input type="password" name="password_confirmation" /></td>
+ </tr>
+ </table>
+ <input type="submit" value="Create an account">
+ </form>
+</div>
+<%= erb :footer %>
+
+
28 views/profile.erb
@@ -0,0 +1,28 @@
+<%= erb :header %>
+
+<div class="span-16" id="maincol">
+ <h2><%= @user.username %></h2>
+ <% if @logged_in_user != @user %>
+ <div class="box">
+ <% if @logged_in_user.following?(@user) %>
+ <a href="/<%= @logged_in_user.username %>/stopfollow/<%= @user.username %>">Stop following</a>
+ <% else %>
+ <a href="/<%= @logged_in_user.username %>/follow/<%= @user.username %>">Follow</a>
+ <% end %>
+ </div>
+ <% end %>
+
+ <%= erb :_posts %>
+</div>
+<div class="span-7 last" id="rightcol">
+ <div class="span-7 box">
+ <h4><%= pluralize("follower", "followers", @followers.length) %></h4>
+ <%= erb :_user_list, :locals => {:users => @followers } %>
+ </div>
+ <div class="span-7 box">
+ <h4>Following <%= @followees.length %></h4>
+ <%= erb :_user_list, :locals => {:users => @followees } %>
+ </div>
+</div>
+
+<%= erb :footer %>
18 views/timeline.erb
@@ -0,0 +1,18 @@
+
+<%= erb :header %>
+
+<div class="span-16" id="maincol">
+ <h3>Timeline</h3>
+
+ Posts from all users.
+ <br />
+ <%= erb :_posts %>
+</div>
+<div class="span-7 last" id="rightcol">
+ <div class="span-7 box">
+ <h4>New users</h4>
+ <%= erb :_user_list, :locals => {:users => User.new_users} %>
+ </div>
+</div>
+<%= erb :footer %>
+
Please sign in to comment.
Something went wrong with that request. Please try again.