diff --git a/app/controllers/aspects_controller.rb b/app/controllers/aspects_controller.rb
index d755a72685f6b4de2262336e652ab2187f0417af..ed2d1faa1f199827b5e678881d4ce8ad0f874d74 100644
--- a/app/controllers/aspects_controller.rb
+++ b/app/controllers/aspects_controller.rb
@@ -31,7 +31,7 @@ class AspectsController < ApplicationController
       @aspect_ids = @aspects.map{|a| a.id}
 
       @posts = StatusMessage.joins(:aspects).where(:pending => false,
-               :aspects => {:id => @aspect_ids}).includes(:comments, :photos).select('DISTINCT `posts`.*').paginate(
+               :aspects => {:id => @aspect_ids}).includes(:comments, :photos, :likes, :dislikes).select('DISTINCT `posts`.*').paginate(
                :page => params[:page], :per_page => 15, :order => sort_order + ' DESC')
       @fakes = PostsFake.new(@posts)
 
diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb
index 6e2ac8f5e4063f0a58e8e861e2aaa26d16cce77a..9f66f2b1524c5a924d704d7c10acae70bd864ca5 100644
--- a/app/controllers/comments_controller.rb
+++ b/app/controllers/comments_controller.rb
@@ -37,10 +37,10 @@ class CommentsController < ApplicationController
           format.mobile{ redirect_to status_message_path(@comment.post_id) }
         end
       else
-        render :nothing => true, :status => 406
+        render :nothing => true, :status => 422
       end
     else
-      render :nothing => true, :status => 406
+      render :nothing => true, :status => 422
     end
   end
 
diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6eef3bebb6d885c0c65723629e6d2fa9121c5550
--- /dev/null
+++ b/app/controllers/likes_controller.rb
@@ -0,0 +1,44 @@
+#   Copyright (c) 2010, Diaspora Inc.  This file is
+#   licensed under the Affero General Public License version 3 or later.  See
+#   the COPYRIGHT file.
+
+class LikesController < ApplicationController
+  include ApplicationHelper
+  before_filter :authenticate_user!
+  
+  respond_to :html, :mobile, :json
+  
+  def create
+    target = current_user.find_visible_post_by_id params[:post_id]
+    positive = (params[:positive] == 'true') ? true : false
+    if target
+      @like = current_user.build_like(positive, :on => target)
+
+      if @like.save
+        Rails.logger.info("event=create type=like user=#{current_user.diaspora_handle} status=success like=#{@like.id} positive=#{positive}")
+        Postzord::Dispatch.new(current_user, @like).post
+
+        respond_to do |format|
+          format.js {
+            json = { :post_id => @like.post_id,
+                     :html => render_to_string(
+                       :partial => 'likes/likes',
+                       :locals => {
+                         :likes => @like.post.likes,
+                         :dislikes => @like.post.dislikes
+                       }
+                     )
+                   }
+            render(:json => json, :status => 201)
+          }
+          format.html { render :nothing => true, :status => 201 }
+          format.mobile { redirect_to status_message_path(@like.post_id) }
+        end
+      else
+        render :nothing => true, :status => 422
+      end
+    else
+      render :nothing => true, :status => 422
+    end
+  end
+end
diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb
index 17ec166342684c66f71679b7e8e8051480899054..13fa0f488fc4b206a3c3232a5c2d9e9aa32fb442 100644
--- a/app/controllers/photos_controller.rb
+++ b/app/controllers/photos_controller.rb
@@ -111,10 +111,10 @@ class PhotosController < ApplicationController
                             :status => 201}
         end
       else
-        render :nothing => true, :status => 406
+        render :nothing => true, :status => 422
       end
     else
-      render :nothing => true, :status => 406
+      render :nothing => true, :status => 422
     end
   end
 
diff --git a/app/helpers/likes_helper.rb b/app/helpers/likes_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fde7be672fa00c021f2648a9886ec0ffeceec499
--- /dev/null
+++ b/app/helpers/likes_helper.rb
@@ -0,0 +1,10 @@
+#   Copyright (c) 2010, Diaspora Inc.  This file is
+#   licensed under the Affero General Public License version 3 or later.  See
+#   the COPYRIGHT file.
+
+module LikesHelper
+  def likes_list likes
+    links = likes.collect { |like| link_to "#{h(like.author.name.titlecase)}", person_path(like.author) }
+    links.join(", ").html_safe
+  end
+end
diff --git a/app/helpers/sockets_helper.rb b/app/helpers/sockets_helper.rb
index 8202ee9f3d43db5e6e690d4b9052b4de633efaf0..92b5fa11fc391b73347b9fb3b7f3c7f13b8d16bf 100644
--- a/app/helpers/sockets_helper.rb
+++ b/app/helpers/sockets_helper.rb
@@ -50,6 +50,9 @@ module SocketsHelper
       elsif object.is_a? Comment
         v = render_to_string(:partial => 'comments/comment', :locals => {:comment => object, :person => object.author})
 
+      elsif object.is_a? Like
+        v = render_to_string(:partial => 'likes/likes', :locals => {:likes => object.post.likes, :dislikes => object.post.dislikes})
+
       elsif object.is_a? Notification
         v = render_to_string(:partial => 'notifications/popup', :locals => {:note => object, :person => opts[:actor]})
 
@@ -74,6 +77,10 @@ module SocketsHelper
 
     end
 
+    if object.is_a? Like
+      action_hash[:post_guid] = object.post.guid
+    end
+
     action_hash[:mine?] = object.author && (object.author.owner_id == uid) if object.respond_to?(:author)
 
     I18n.locale = old_locale unless user.nil?
diff --git a/app/models/like.rb b/app/models/like.rb
new file mode 100644
index 0000000000000000000000000000000000000000..60ac461d9ad43065faa05c6ab8a5d3262666c73e
--- /dev/null
+++ b/app/models/like.rb
@@ -0,0 +1,42 @@
+#   Copyright (c) 2010, Diaspora Inc.  This file is
+#   licensed under the Affero General Public License version 3 or later.  See
+#   the COPYRIGHT file.
+
+class Like < ActiveRecord::Base
+  require File.join(Rails.root, 'lib/diaspora/web_socket')
+  include ROXML
+  
+  include Diaspora::Webhooks
+  include Diaspora::Relayable
+  include Diaspora::Guid
+  
+  include Diaspora::Socketable
+  
+  xml_attr :positive
+  xml_attr :diaspora_handle
+  
+  belongs_to :post
+  belongs_to :author, :class_name => 'Person'
+
+  validates_uniqueness_of :post_id, :scope => :author_id
+
+  def diaspora_handle
+    self.author.diaspora_handle
+  end
+  
+  def diaspora_handle= nh
+    self.author = Webfinger.new(nh).fetch
+  end
+  
+  def parent_class
+    Post
+  end
+  
+  def parent
+    self.post
+  end
+  
+  def parent= parent
+    self.post = parent
+  end
+end
diff --git a/app/models/post.rb b/app/models/post.rb
index dd103edc00728a5cfe899c5cc2845a13ec209301..627496a68885da5982ba768c252d883fa2d08c5a 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -14,6 +14,8 @@ class Post < ActiveRecord::Base
   xml_attr :created_at
 
   has_many :comments, :order => 'created_at ASC'
+  has_many :likes, :conditions => '`likes`.`positive` = 1'
+  has_many :dislikes, :conditions => '`likes`.`positive` = 0', :class_name => 'Like'
   has_many :post_visibilities
   has_many :aspects, :through => :post_visibilities
   has_many :mentions, :dependent => :destroy
diff --git a/app/models/user.rb b/app/models/user.rb
index cc9ec26095e5f61eade0f43f7cf6d19ce284ac84..ffcf0d2ce98f0d3f313dbdc46318449c4d7b7ef2 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -176,6 +176,32 @@ class User < ActiveRecord::Base
     comment
   end
 
+  ######## Liking  ########
+  def build_like(positive, options = {})
+    like = Like.new(:author_id => self.person.id,
+                    :positive => positive,
+                    :post => options[:on])
+    like.set_guid
+    #sign like as liker
+    like.author_signature = like.sign_with_key(self.encryption_key)
+
+    if !like.post_id.blank? && person.owns?(like.parent)
+      #sign like as post owner
+      like.parent_author_signature = like.sign_with_key(self.encryption_key)
+    end
+
+    like
+  end
+
+  def liked?(post)
+    [post.likes, post.dislikes].each do |likes|
+      likes.each do |like|
+        return true if like.author_id == self.person.id
+      end
+    end
+    return false
+  end
+
   ######### Mailer #######################
   def mail(job, *args)
     pref = job.to_s.gsub('Job::Mail', '').underscore
diff --git a/app/views/likes/_likes.haml b/app/views/likes/_likes.haml
new file mode 100644
index 0000000000000000000000000000000000000000..2c1ebe1e8520ed1d12e6aa683d5aa2cf6bbef674
--- /dev/null
+++ b/app/views/likes/_likes.haml
@@ -0,0 +1,17 @@
+-#   Copyright (c) 2010, Diaspora Inc.  This file is
+-#   licensed under the Affero General Public License version 3 or later.  See
+-#   the COPYRIGHT file.
+
+- if likes.length > 0
+  .likes
+    = image_tag('icons/happy_smiley.png')
+    = link_to t('.people_like_this', :count => likes.length), "#", :class => "expand_likes"
+    %span.hidden.likes_list
+      = likes_list(likes)
+
+- if dislikes.length > 0
+  .dislikes
+    = image_tag('icons/sad_smiley.png')
+    = link_to t('.people_dislike_this', :count => dislikes.length), "#", :class => "expand_dislikes"
+    %span.hidden.dislikes_list
+      = likes_list(dislikes)
diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml
index fc967e74fe52e39355dbd3fdf7bf484a9e405020..3aa7fb7378ba395f73eca7930504d1e4c30be338 100644
--- a/app/views/photos/show.html.haml
+++ b/app/views/photos/show.html.haml
@@ -66,5 +66,13 @@
     = t('_comments')
 
   #photo_stream.stream.show
+    - if (defined?(current_user) && !current_user.liked?(@parent))
+      %span.like_links
+        = link_to t('shared.stream_element.like'), {:controller => "likes", :action => "create", :positive => 'true', :post_id => @parent.id }, :class => "like_it", :remote => true
+        |
+        = link_to t('shared.stream_element.dislike'), {:controller => "likes", :action =>  "create", :positive => 'false', :post_id => @parent.id }, :class => "dislike_it", :remote => true
+
     %div{:data=>{:guid=>@parent.id}}
+      .likes_container
+        = render "likes/likes", :post_id => @parent.id, :likes => @parent.likes, :dislikes => @parent.dislikes
       = render "comments/comments", :post_id => @parent.id, :comments => @parent.comments, :always_expanded => true
diff --git a/app/views/shared/_stream_element.html.haml b/app/views/shared/_stream_element.html.haml
index d65ac48e34262331391dfb493a2c5713912892e2..eca6529f4258ace5fc8bb6f1331dab4f665a1f28 100644
--- a/app/views/shared/_stream_element.html.haml
+++ b/app/views/shared/_stream_element.html.haml
@@ -32,6 +32,15 @@
         = link_to(how_long_ago(post), status_message_path(post))
 
       - unless (defined?(@commenting_disabled) && @commenting_disabled)
-        = link_to t('comments.new_comment.comment').downcase, '#', :class => 'focus_comment_textarea'
+        = link_to t('comments.new_comment.comment'), '#', :class => 'focus_comment_textarea'
+        - if (defined?(current_user) && !current_user.liked?(post))
+          %span.like_links
+            |
+            = link_to t('.like'), {:controller => "likes", :action => "create", :positive => 'true', :post_id => post.id }, :class => "like_it", :remote => true
+            |
+            = link_to t('.dislike'), {:controller => "likes", :action =>  "create", :positive => 'false', :post_id => post.id }, :class => "dislike_it", :remote => true
+
+    .likes_container
+      = render "likes/likes", :post_id => post.id, :likes => post.likes, :dislikes => post.dislikes, :current_user => current_user
 
     = render "comments/comments", :post_id => post.id, :comments => post.comments, :current_user => current_user, :condensed => true, :commenting_disabled => (defined?(@commenting_disabled) && @commenting_disabled)
diff --git a/config/locales/diaspora/en.yml b/config/locales/diaspora/en.yml
index 4dc2743b720e6aa4c5fa122643c1ff0365284a3c..dc5d9c6d50378425655c9a7bed0a1f14a4a64d27 100644
--- a/config/locales/diaspora/en.yml
+++ b/config/locales/diaspora/en.yml
@@ -157,7 +157,7 @@ en:
     many: "%{count} comments"
     other: "%{count} comments"
     new_comment:
-      comment: "Comment"
+      comment: "comment"
       commenting: "Commenting..."
 
   contacts:
@@ -262,6 +262,22 @@ en:
       toggle: "toggle mobile site"
       public_feed: "Public Diaspora Feed for %{name}"
 
+
+  likes:
+    likes:
+      people_like_this:
+        zero: "no people liked this"
+        one: "1 person liked this"
+        few: "%{count} people liked this"
+        many: "%{count} people liked this"
+        other: "%{count} people liked this"
+      people_dislike_this:
+        zero: "no people disliked this"
+        one: "1 person disliked this"
+        few: "%{count} people disliked this"
+        many: "%{count} people disliked this"
+        other: "%{count} people disliked this"
+
   notifications:
     request_accepted: "accepted your share request."
     new_request: "offered to share with you."
@@ -537,6 +553,9 @@ en:
     contact_list:
       all_contacts: "All contacts"
       cannot_remove: "Cannot remove person from last aspect. (If you want to disconnect from this person you must remove contact.)"
+    stream_element:
+      like: "I like this"
+      dislike: "I dislike this"
 
   status_messages:
     new:
diff --git a/config/routes.rb b/config/routes.rb
index ad70d3e2337c96d77d21b82f7a28378d549ac657..2cf65a096084873d350eda1cd46643da36376247 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -6,6 +6,8 @@ Diaspora::Application.routes.draw do
   resources :status_messages, :only => [:new, :create, :destroy, :show]
   resources :comments,        :only => [:create]
   resources :requests,        :only => [:destroy, :create]
+  match '/likes' => 'likes#create'
+  resources :likes,           :only => [:create]
 
   match 'tags/:name' => 'tags#show'
   resources :tags, :only => [:show]
diff --git a/db/migrate/201110319172136_add_likes.rb b/db/migrate/201110319172136_add_likes.rb
new file mode 100644
index 0000000000000000000000000000000000000000..0cf481ae4f764ee9b02c57ac7cba7d8183949b27
--- /dev/null
+++ b/db/migrate/201110319172136_add_likes.rb
@@ -0,0 +1,21 @@
+class AddLikes < ActiveRecord::Migration
+  def self.up
+    create_table :likes do |t|
+      t.boolean :positive, :default => true
+      t.integer :post_id
+      t.integer :author_id
+      t.string :guid
+      t.text :author_signature
+      t.text :parent_author_signature
+      t.timestamps
+    end
+    add_index :likes, :guid, :unique => true
+    add_index :likes, :post_id
+    add_foreign_key(:likes, :posts, :dependant => :delete)
+    add_foreign_key(:likes, :people, :column =>  :author_id, :dependant => :delete)
+  end
+
+  def self.down
+    drop_table :likes
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a55b15a5c377b73f6fd52ed69334a3ee03ac6e31..ca201bb39e70a30552422b2ab776487422a8a617 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
 #
 # It's strongly recommended to check this file into your version control system.
 
-ActiveRecord::Schema.define(:version => 20110319005509) do
+ActiveRecord::Schema.define(:version => 201110319172136) do
 
   create_table "aspect_memberships", :force => true do |t|
     t.integer  "aspect_id",  :null => false
@@ -105,6 +105,21 @@ ActiveRecord::Schema.define(:version => 20110319005509) do
   add_index "invitations", ["recipient_id"], :name => "index_invitations_on_recipient_id"
   add_index "invitations", ["sender_id"], :name => "index_invitations_on_sender_id"
 
+  create_table "likes", :force => true do |t|
+    t.boolean  "positive",                :default => true
+    t.integer  "post_id"
+    t.integer  "author_id"
+    t.string   "guid"
+    t.text     "author_signature"
+    t.text     "parent_author_signature"
+    t.datetime "created_at"
+    t.datetime "updated_at"
+  end
+
+  add_index "likes", ["author_id"], :name => "likes_author_id_fk"
+  add_index "likes", ["guid"], :name => "index_likes_on_guid", :unique => true
+  add_index "likes", ["post_id"], :name => "index_likes_on_post_id"
+
   create_table "mentions", :force => true do |t|
     t.integer "post_id",   :null => false
     t.integer "person_id", :null => false
@@ -342,6 +357,9 @@ ActiveRecord::Schema.define(:version => 20110319005509) do
   add_foreign_key "invitations", "users", :name => "invitations_recipient_id_fk", :column => "recipient_id", :dependent => :delete
   add_foreign_key "invitations", "users", :name => "invitations_sender_id_fk", :column => "sender_id", :dependent => :delete
 
+  add_foreign_key "likes", "people", :name => "likes_author_id_fk", :column => "author_id"
+  add_foreign_key "likes", "posts", :name => "likes_post_id_fk"
+
   add_foreign_key "notification_actors", "notifications", :name => "notification_actors_notification_id_fk", :dependent => :delete
 
   add_foreign_key "posts", "people", :name => "posts_author_id_fk", :column => "author_id", :dependent => :delete
diff --git a/public/images/icons/happy_smiley.png b/public/images/icons/happy_smiley.png
new file mode 100644
index 0000000000000000000000000000000000000000..f9b44488d3b8e9f3cf70981f6d00f69386c03796
Binary files /dev/null and b/public/images/icons/happy_smiley.png differ
diff --git a/public/images/icons/sad_smiley.png b/public/images/icons/sad_smiley.png
new file mode 100644
index 0000000000000000000000000000000000000000..3e2de91c5b5a7694f27d276ba5073ba43f4773f5
Binary files /dev/null and b/public/images/icons/sad_smiley.png differ
diff --git a/public/javascripts/stream.js b/public/javascripts/stream.js
index b19c6f75c58f29955a12b9d39d2f2e2b42984e63..17e4eaba87f5ff713a370f7ad8ddbd12f06293e5 100644
--- a/public/javascripts/stream.js
+++ b/public/javascripts/stream.js
@@ -39,6 +39,32 @@ var Stream = {
       }
     });
 
+    // like/dislike
+    $stream.delegate("a.expand_likes", "click", function(evt) {
+      evt.preventDefault();
+      $(this).siblings('.likes_list').fadeToggle('fast');
+    });
+
+    $stream.delegate("a.expand_dislikes", "click", function(evt) {
+      evt.preventDefault();
+      $(this).siblings('.dislikes_list').fadeToggle('fast');
+    });
+
+    $(".like_it, .dislike_it").live('ajax:loading', function(data, json, xhr) {
+      $(this).parent().fadeOut('fast');
+    });
+
+    $(".like_it, .dislike_it").live('ajax:success', function(data, json, xhr) {
+      $(this).parent().detach();
+      json = $.parseJSON(json);
+      WebSocketReceiver.processLike(json.post_id, json.html);
+    });
+    
+    $('.like_it, .dislike_it').live('ajax:failure', function(data, html, xhr) {
+      Diaspora.widgets.alert.alert('Failed to like/dislike!');
+      $(this).parent().fadeIn('fast');
+    });
+
     // reshare button action
     $stream.delegate(".reshare_button", "click", function(evt) {
       evt.preventDefault();
diff --git a/public/javascripts/web-socket-receiver.js b/public/javascripts/web-socket-receiver.js
index cb888f857ddd81882f88316a95c9328191cad209..4944198fc8b006ea01f0ca8cc3f3f5f954b16f2f 100644
--- a/public/javascripts/web-socket-receiver.js
+++ b/public/javascripts/web-socket-receiver.js
@@ -36,6 +36,9 @@ var WebSocketReceiver = {
             'my_post?': obj['my_post?']
           });
 
+        } else if (obj['class']=="likes") {
+          WebSocketReceiver.processLike(obj.post_id, obj.html)
+
         } else {
           WebSocketReceiver.processPost(obj['class'], obj.post_id, obj.html, obj.aspect_ids);
         }
@@ -116,6 +119,11 @@ var WebSocketReceiver = {
     Diaspora.widgets.timeago.updateTimeAgo();
   },
 
+  processLike: function(postId, html) {
+    var post = $("*[data-guid='"+postId+"']");
+    $(".likes_container", post).fadeOut('fast').html(html).fadeIn('fast');
+  },
+
   processPost: function(className, postId, html, aspectIds) {
     if(WebSocketReceiver.onPageForAspects(aspectIds)) {
       WebSocketReceiver.addPostToStream(postId, html);
diff --git a/public/stylesheets/sass/application.sass b/public/stylesheets/sass/application.sass
index 9e801f8db4f0aff41e89965d3926d1796bd9c304..c993eca687a60db70057631d68914f2361318543 100644
--- a/public/stylesheets/sass/application.sass
+++ b/public/stylesheets/sass/application.sass
@@ -563,7 +563,9 @@ header
     :align right
 
 ul.comments,
-ul.show_comments
+ul.show_comments,
+div.likes,
+div.dislikes
   :margin 0
     :top 0.5em
   :padding 0
@@ -2320,7 +2322,9 @@ h3,h4
   :position relative
   :z-index 10
 
-ul.show_comments
+ul.show_comments,
+div.likes,
+div.dislikes
   :margin
     :bottom -0.5em
   > li
@@ -2852,3 +2856,16 @@ h1.tag
   .share_with
     :background
       :color rgb(245,245,245)
+
+.likes_container
+  .likes,
+  .dislikes
+    :border-bottom 1px solid white
+    a
+      :padding 1px
+      :vertical-align middle
+      :font-size smaller
+    img
+      :width 12px
+      :height 12px
+      :margin-left 0.5em
diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb
index 0f2b3897e6cd5cea8ac7828db7a9a1b2d7392bcf..9682b40e504925a00c8ae6f1755f25b3ed1d0f86 100644
--- a/spec/controllers/comments_controller_spec.rb
+++ b/spec/controllers/comments_controller_spec.rb
@@ -61,7 +61,7 @@ describe CommentsController do
       it 'posts no comment' do
         @user1.should_not_receive(:comment)
         post :create, comment_hash
-        response.code.should == '406'
+        response.code.should == '422'
       end
     end
   end
diff --git a/spec/controllers/likes_controller_spec.rb b/spec/controllers/likes_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..afa2d8df92c1810fbed0f16c1337335431b39148
--- /dev/null
+++ b/spec/controllers/likes_controller_spec.rb
@@ -0,0 +1,68 @@
+#   Copyright (c) 2010, Diaspora Inc.  This file is
+#   licensed under the Affero General Public License version 3 or later.  See
+#   the COPYRIGHT file.
+
+require 'spec_helper'
+
+describe LikesController do
+  render_views
+
+  before do
+    @user1 = alice
+    @user2 = bob
+
+    @aspect1 = @user1.aspects.first
+    @aspect2 = @user2.aspects.first
+
+    sign_in :user, @user1
+  end
+
+  describe '#create' do
+    let(:like_hash) {
+      {:positive => 1,
+       :post_id => "#{@post.id}"}
+    }
+    let(:dislike_hash) {
+      {:positive => 0,
+       :post_id => "#{@post.id}"}
+    }
+    context "on my own post" do
+      before do
+        @post = @user1.post :status_message, :text => "AWESOME", :to => @aspect1.id
+      end
+      it 'responds to format js' do
+        post :create, like_hash.merge(:format => 'js')
+        response.code.should == '201'
+      end
+    end
+
+    context "on a post from a contact" do
+      before do
+        @post = @user2.post :status_message, :text => "AWESOME", :to => @aspect2.id
+      end
+      it 'likes' do
+        post :create, like_hash
+        response.code.should == '201'
+      end
+      it 'dislikes' do
+        post :create, dislike_hash
+        response.code.should == '201'
+      end
+      it "doesn't post multiple times" do
+        @user1.like(1, :on => @post)
+        post :create, dislike_hash
+        response.code.should == '422'
+      end
+    end
+    context "on a post from a stranger" do
+      before do
+        @post = eve.post :status_message, :text => "AWESOME", :to => eve.aspects.first.id
+      end
+      it "doesn't post" do
+        @user1.should_not_receive(:like)
+        post :create, like_hash
+        response.code.should == '422'
+      end
+    end
+  end
+end
diff --git a/spec/controllers/photos_controller_spec.rb b/spec/controllers/photos_controller_spec.rb
index bc795ad77c0cf302beae40329dda35269da68d3f..9b098b2dbd74bc08b43f450462b12667dac56ad4 100644
--- a/spec/controllers/photos_controller_spec.rb
+++ b/spec/controllers/photos_controller_spec.rb
@@ -162,9 +162,9 @@ describe PhotosController do
       response.code.should == "201"
     end
 
-    it 'should return a 406 on failure' do
+    it 'should return a 422 on failure' do
       get :make_profile_photo, :photo_id => @bobs_photo.id
-      response.code.should == "406"
+      response.code.should == "422"
     end
 
   end
diff --git a/spec/models/like_spec.rb b/spec/models/like_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9fea70324b5082f1ce25958c576fbc83f2d589a9
--- /dev/null
+++ b/spec/models/like_spec.rb
@@ -0,0 +1,80 @@
+#   Copyright (c) 2010, Diaspora Inc.  This file is
+#   licensed under the Affero General Public License version 3 or later.  See
+#   the COPYRIGHT file.
+
+require 'spec_helper'
+require File.join(Rails.root, "spec", "shared_behaviors", "relayable")
+
+describe Like do
+  before do
+    @alices_aspect = alice.aspects.first
+    @bobs_aspect = bob.aspects.first
+
+    @bob = bob
+    @eve = eve
+    @status = alice.post(:status_message, :text => "hello", :to => @alices_aspect.id)
+  end
+
+  describe 'User#like' do
+    it "should be able to like on one's own status" do
+      alice.like(1, :on => @status)
+      @status.reload.likes.first.positive.should == true
+    end
+
+    it "should be able to like on a contact's status" do
+      bob.like(0, :on => @status)
+      @status.reload.dislikes.first.positive.should == false
+    end
+
+    it "does not allow multiple likes" do
+      lambda {
+        alice.like(1, :on => @status)
+        alice.like(0, :on => @status)
+      }.should raise_error
+    end
+  end
+
+  describe 'xml' do
+    before do
+      @liker = Factory.create(:user)
+      @liker_aspect = @liker.aspects.create(:name => "dummies")
+      connect_users(alice, @alices_aspect, @liker, @liker_aspect)
+      @post = alice.post :status_message, :text => "huhu", :to => @alices_aspect.id
+      @like = @liker.like 0, :on => @post
+      @xml = @like.to_xml.to_s
+    end
+    it 'serializes the sender handle' do
+      @xml.include?(@liker.diaspora_handle).should be_true
+    end
+    it' serializes the post_guid' do
+      @xml.should include(@post.guid)
+    end
+    describe 'marshalling' do
+      before do
+        @marshalled_like = Like.from_xml(@xml)
+      end
+      it 'marshals the author' do
+        @marshalled_like.author.should == @liker.person
+      end
+      it 'marshals the post' do
+        @marshalled_like.post.should == @post
+      end
+    end
+  end
+
+  describe 'it is relayable' do
+    before do
+      @local_luke, @local_leia, @remote_raphael = set_up_friends
+      @remote_parent = Factory.create(:status_message, :author => @remote_raphael)
+      @local_parent = @local_luke.post :status_message, :text => "foobar", :to => @local_luke.aspects.first
+    
+      @object_by_parent_author = @local_luke.like(1, :on => @local_parent)
+      @object_by_recipient = @local_leia.build_like(1, :on => @local_parent)
+      @dup_object_by_parent_author = @object_by_parent_author.dup
+    
+      @object_on_remote_parent = @local_luke.like(0, :on => @remote_parent)
+    end
+    it_should_behave_like 'it is relayable'
+  end
+
+end
diff --git a/spec/support/user_methods.rb b/spec/support/user_methods.rb
index 975d7c5d9e3eaebca9dc820b787567f158fb9950..4382b752b355558a38b800e532bfc951d2db26f0 100644
--- a/spec/support/user_methods.rb
+++ b/spec/support/user_methods.rb
@@ -37,6 +37,16 @@ class User
     end
   end
 
+  def like(positive, options ={})
+    fantasy_resque do
+      l = build_like(positive, options)
+      if l.save!
+        Postzord::Dispatch.new(self, l).post
+      end
+      l
+    end
+  end
+
   def post_at_time(time)
     p = self.post(:status_message, :text => 'hi', :to => self.aspects.first)
     p.created_at = time