From f8d82724067fd464d65ba5e07c6ef01fb848beee Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Wed, 6 Mar 2013 15:17:07 -0500
Subject: [PATCH] Cleaned up TopicUserSpec, introduces clearing of pinned
 topics

---
 Gemfile                                       |    2 +-
 Gemfile.lock                                  |    2 +-
 .../discourse/controllers/topic_controller.js |    9 +
 .../javascripts/discourse/models/topic.js     |   21 +
 .../views/topic_footer_buttons_view.js        |   24 +
 app/controllers/topics_controller.rb          |   23 +-
 app/models/post.rb                            |    2 +-
 app/models/post_alert_observer.rb             |    4 +-
 app/models/topic.rb                           |   41 +-
 app/models/topic_user.rb                      |  314 ++-
 app/models/user.rb                            |   32 +-
 app/serializers/category_topic_serializer.rb  |    1 -
 app/serializers/topic_list_item_serializer.rb |    6 +
 app/serializers/topic_view_serializer.rb      |   10 +-
 config/locales/client.en.yml                  |    4 +
 config/routes.rb                              |    1 +
 ...80148_add_cleared_pinned_to_topic_users.rb |    9 +
 db/structure.sql                              | 2049 ++++++++++++++++-
 lib/pinned_check.rb                           |   24 +
 lib/topic_query.rb                            |   97 +-
 lib/unread.rb                                 |    2 +-
 spec/components/pinned_check_spec.rb          |   58 +
 spec/components/topic_query_spec.rb           |   16 +-
 spec/components/unread_spec.rb                |    6 +-
 spec/controllers/topics_controller_spec.rb    |   31 +
 spec/models/topic_spec.rb                     |   18 +-
 spec/models/topic_user_spec.rb                |  199 +-
 27 files changed, 2663 insertions(+), 342 deletions(-)
 create mode 100644 db/migrate/20130306180148_add_cleared_pinned_to_topic_users.rb
 create mode 100644 lib/pinned_check.rb
 create mode 100644 spec/components/pinned_check_spec.rb

diff --git a/Gemfile b/Gemfile
index 0c1ade108..fac8f5ac7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,4 +1,4 @@
-source 'https://rubygems.org'
+source 'http://rubygems.org'
 
 gem 'active_model_serializers', git: 'git://github.com/rails-api/active_model_serializers.git'
 gem 'ember-rails', git: 'git://github.com/emberjs/ember-rails.git' # so we get the pre version
diff --git a/Gemfile.lock b/Gemfile.lock
index 3684482f2..15802713a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -71,7 +71,7 @@ PATH
       rails (~> 3.1)
 
 GEM
-  remote: https://rubygems.org/
+  remote: http://rubygems.org/
   specs:
     actionmailer (3.2.12)
       actionpack (= 3.2.12)
diff --git a/app/assets/javascripts/discourse/controllers/topic_controller.js b/app/assets/javascripts/discourse/controllers/topic_controller.js
index f1c503b0d..cd0e48f4b 100644
--- a/app/assets/javascripts/discourse/controllers/topic_controller.js
+++ b/app/assets/javascripts/discourse/controllers/topic_controller.js
@@ -255,6 +255,15 @@ Discourse.TopicController = Discourse.ObjectController.extend({
     this.get('content').toggleStar();
   },
 
+  /**
+    Clears the pin from a topic for the currentUser
+
+    @method clearPin
+  **/
+  clearPin: function() {
+    this.get('content').clearPin();
+  },
+
   // Receive notifications for this topic
   subscribe: function() {
     var bus,
diff --git a/app/assets/javascripts/discourse/models/topic.js b/app/assets/javascripts/discourse/models/topic.js
index 5dc912a8e..937ab5824 100644
--- a/app/assets/javascripts/discourse/models/topic.js
+++ b/app/assets/javascripts/discourse/models/topic.js
@@ -329,6 +329,27 @@ Discourse.Topic = Discourse.Model.extend({
     });
   },
 
+  /**
+    Clears the pin from a topic for the currentUser
+
+    @method clearPin
+  **/
+  clearPin: function() {
+
+    var topic = this;
+
+    // Clear the pin optimistically from the object
+    topic.set('pinned', false);
+
+    $.ajax("/t/" + this.get('id') + "/clear-pin", {
+      type: 'PUT',
+      error: function() {
+        // On error, put the pin back
+        topic.set('pinned', true);
+      }
+    });
+  },
+
   // Is the reply to a post directly below it?
   isReplyDirectlyBelow: function(post) {
     var postBelow, posts;
diff --git a/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js b/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js
index c2fe4e777..a68ee7ca4 100644
--- a/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js
+++ b/app/assets/javascripts/discourse/views/topic_footer_buttons_view.js
@@ -68,6 +68,30 @@ Discourse.TopicFooterButtonsView = Ember.ContainerView.extend({
             buffer.push("<i class='icon icon-share'></i>");
           }
         }));
+
+        // Add our clear pin button
+        this.addObject(Discourse.ButtonView.createWithMixins({
+          textKey: 'topic.clear_pin.title',
+          helpKey: 'topic.clear_pin.help',
+          classNameBindings: ['unpinned'],
+
+          // Hide the button if it becomes unpinned
+          unpinned: function() {
+            // When not logged in don't show the button
+            if (!Discourse.get('currentUser')) return 'hidden'
+
+            return this.get('controller.pinned') ? null : 'hidden';
+          }.property('controller.pinned'),
+
+          click: function(buffer) {
+            this.get('controller').clearPin();
+          },
+
+          renderIcon: function(buffer) {
+            buffer.push("<i class='icon icon-pushpin'></i>");
+          }
+        }));
+
       }
 
       this.addObject(Discourse.ButtonView.createWithMixins({
diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb
index 8381ed13d..2ddb7868d 100644
--- a/app/controllers/topics_controller.rb
+++ b/app/controllers/topics_controller.rb
@@ -14,7 +14,9 @@ class TopicsController < ApplicationController
                                           :mute,
                                           :unmute,
                                           :set_notifications,
-                                          :move_posts]
+                                          :move_posts,
+                                          :clear_pin]
+
   before_filter :consider_user_for_promotion, only: :show
 
   skip_before_filter :check_xhr, only: [:avatar, :show, :feed]
@@ -127,16 +129,21 @@ class TopicsController < ApplicationController
     end
   end
 
+  def clear_pin
+    topic = Topic.where(id: params[:topic_id].to_i).first
+    guardian.ensure_can_see!(topic)
+    topic.clear_pin_for(current_user)
+    render nothing: true
+  end
+
   def timings
-
     PostTiming.process_timings(
-        current_user,
-        params[:topic_id].to_i,
-        params[:highest_seen].to_i,
-        params[:topic_time].to_i,
-        (params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
+      current_user,
+      params[:topic_id].to_i,
+      params[:highest_seen].to_i,
+      params[:topic_time].to_i,
+      (params[:timings] || []).map{|post_number, t| [post_number.to_i, t.to_i]}
     )
-
     render nothing: true
   end
 
diff --git a/app/models/post.rb b/app/models/post.rb
index fc42d8d21..82cf60d74 100644
--- a/app/models/post.rb
+++ b/app/models/post.rb
@@ -54,7 +54,7 @@ class Post < ActiveRecord::Base
   after_commit :store_unique_post_key, on: :create
 
   after_create do
-    TopicUser.auto_track(user_id, topic_id, TopicUser::NotificationReasons::CREATED_POST)
+    TopicUser.auto_track(user_id, topic_id, TopicUser.notification_reasons[:created_post])
   end
 
   scope :by_newest, order('created_at desc, id desc')
diff --git a/app/models/post_alert_observer.rb b/app/models/post_alert_observer.rb
index c421122d8..88c7bca90 100644
--- a/app/models/post_alert_observer.rb
+++ b/app/models/post_alert_observer.rb
@@ -80,7 +80,7 @@ class PostAlertObserver < ActiveRecord::Observer
       return unless Guardian.new(user).can_see?(post)
 
       # skip if muted on the topic
-      return if TopicUser.get(post.topic, user).try(:notification_level) == TopicUser::NotificationLevel::MUTED
+      return if TopicUser.get(post.topic, user).try(:notification_level) == TopicUser.notification_levels[:muted]
 
       # Don't notify the same user about the same notification on the same post
       return if user.notifications.exists?(notification_type: type, topic_id: post.topic_id, post_number: post.post_number)
@@ -132,7 +132,7 @@ class PostAlertObserver < ActiveRecord::Observer
         exclude_user_ids << extract_mentioned_users(post).map(&:id)
         exclude_user_ids << extract_quoted_users(post).map(&:id)
         exclude_user_ids.flatten!
-        TopicUser.where(topic_id: post.topic_id, notification_level: TopicUser::NotificationLevel::WATCHING).includes(:user).each do |tu|
+        TopicUser.where(topic_id: post.topic_id, notification_level: TopicUser.notification_levels[:watching]).includes(:user).each do |tu|
           create_notification(tu.user, Notification.types[:posted], post) unless exclude_user_ids.include?(tu.user_id)
         end
       end
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 9c9f85cc6..4b0f2ce42 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb
@@ -83,11 +83,9 @@ class Topic < ActiveRecord::Base
 
   after_create do
     changed_to_category(category)
-    TopicUser.change(
-                     user_id, id,
-                     notification_level: TopicUser::NotificationLevel::WATCHING,
-                     notifications_reason_id: TopicUser::NotificationReasons::CREATED_TOPIC
-                    )
+    TopicUser.change(user_id, id,
+                     notification_level: TopicUser.notification_levels[:watching],
+                     notifications_reason_id: TopicUser.notification_reasons[:created_topic])
     if archetype == Archetype.private_message
       DraftSequence.next!(user, Draft::NEW_PRIVATE_MESSAGE)
     else
@@ -206,8 +204,17 @@ class Topic < ActiveRecord::Base
   end
 
   def update_status(property, status, user)
+
     Topic.transaction do
-      update_column(property, status)
+
+      # Special case: if it's pinned, update that
+      if property.to_sym == :pinned
+        update_pinned(status)
+      else
+        # otherwise update the column
+        update_column(property, status)
+      end
+
       key = "topic_statuses.#{property}_"
       key << (status ? 'enabled' : 'disabled')
 
@@ -506,7 +513,7 @@ class Topic < ActiveRecord::Base
 
   # Enable/disable the mute on the topic
   def toggle_mute(user, muted)
-    TopicUser.change(user, self.id, notification_level: muted?(user) ? TopicUser::NotificationLevel::REGULAR : TopicUser::NotificationLevel::MUTED )
+    TopicUser.change(user, self.id, notification_level: muted?(user) ? TopicUser.notification_levels[:regular] : TopicUser.notification_levels[:muted] )
   end
 
   def slug
@@ -526,7 +533,17 @@ class Topic < ActiveRecord::Base
   def muted?(user)
     return false unless user && user.id
     tu = topic_users.where(user_id: user.id).first
-    tu && tu.notification_level == TopicUser::NotificationLevel::MUTED
+    tu && tu.notification_level == TopicUser.notification_levels[:muted]
+  end
+
+  def clear_pin_for(user)
+    return unless user.present?
+
+    TopicUser.change(user.id, id, cleared_pinned_at: Time.now)
+  end
+
+  def update_pinned(status)
+    update_column(:pinned_at, status ? Time.now : nil)
   end
 
   def draft_key
@@ -535,18 +552,18 @@ class Topic < ActiveRecord::Base
 
   # notification stuff
   def notify_watch!(user)
-    TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::WATCHING)
+    TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:watching])
   end
 
   def notify_tracking!(user)
-    TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::TRACKING)
+    TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:tracking])
   end
 
   def notify_regular!(user)
-    TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::REGULAR)
+    TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:regular])
   end
 
   def notify_muted!(user)
-    TopicUser.change(user, id, notification_level: TopicUser::NotificationLevel::MUTED)
+    TopicUser.change(user, id, notification_level: TopicUser.notification_levels[:muted])
   end
 end
diff --git a/app/models/topic_user.rb b/app/models/topic_user.rb
index b92e7bec1..e3d29d0a8 100644
--- a/app/models/topic_user.rb
+++ b/app/models/topic_user.rb
@@ -2,185 +2,173 @@ class TopicUser < ActiveRecord::Base
   belongs_to :user
   belongs_to :topic
 
-  module NotificationLevel
-    WATCHING = 3
-    TRACKING = 2
-    REGULAR = 1
-    MUTED = 0
-  end
+  # Class methods
+  class << self
 
-  module NotificationReasons
-    CREATED_TOPIC = 1
-    USER_CHANGED = 2
-    USER_INTERACTED = 3
-    CREATED_POST = 4
-  end
+    # Enums
+    def notification_levels
+      @notification_levels ||= Enum.new(:muted, :regular, :tracking, :watching, start: 0)
+    end
 
-  def self.auto_track(user_id, topic_id, reason)
-    if TopicUser.where(user_id: user_id, topic_id: topic_id, notifications_reason_id: nil).exists?
-      change(user_id, topic_id,
-          notification_level: NotificationLevel::TRACKING,
+    def notification_reasons
+      @notification_reasons ||= Enum.new(:created_topic, :user_changed, :user_interacted, :created_post)
+    end
+
+    def auto_track(user_id, topic_id, reason)
+      if TopicUser.where(user_id: user_id, topic_id: topic_id, notifications_reason_id: nil).exists?
+        change(user_id, topic_id,
+          notification_level: notification_levels[:tracking],
           notifications_reason_id: reason
-      )
+        )
 
-      MessageBus.publish("/topic/#{topic_id}", {
-        notification_level_change: NotificationLevel::TRACKING,
-        notifications_reason_id: reason
-      }, user_ids: [user_id])
-    end
-  end
-
-
-  # Find the information specific to a user in a forum topic
-  def self.lookup_for(user, topics)
-    # If the user isn't logged in, there's no last read posts
-    return {} if user.blank? || topics.blank?
-
-    topic_ids = topics.map(&:id)
-    create_lookup(TopicUser.where(topic_id: topic_ids, user_id: user.id))
-  end
-
-  def self.create_lookup(topic_users)
-    topic_users = topic_users.to_a
-
-    result = {}
-    return result if topic_users.blank?
-
-    topic_users.each do |ftu|
-      result[ftu.topic_id] = ftu
-    end
-    result
-  end
-
-  def self.get(topic,user)
-    if Topic === topic
-      topic = topic.id
-    end
-    if User === user
-      user = user.id
-    end
-
-    TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
-  end
-
-  # Change attributes for a user (creates a record when none is present). First it tries an update
-  # since there's more likely to be an existing record than not. If the update returns 0 rows affected
-  # it then creates the row instead.
-  def self.change(user_id, topic_id, attrs)
-    # Sometimes people pass objs instead of the ids. We can handle that.
-    topic_id = topic_id.id if topic_id.is_a?(Topic)
-    user_id = user_id.id if user_id.is_a?(User)
-
-    TopicUser.transaction do
-      attrs = attrs.dup
-      attrs[:starred_at] = DateTime.now if attrs[:starred_at].nil? && attrs[:starred]
-
-      if attrs[:notification_level]
-        attrs[:notifications_changed_at] ||= DateTime.now
-        attrs[:notifications_reason_id] ||= TopicUser::NotificationReasons::USER_CHANGED
+        MessageBus.publish("/topic/#{topic_id}", {
+          notification_level_change: notification_levels[:tracking],
+          notifications_reason_id: reason
+        }, user_ids: [user_id])
       end
-      attrs_array = attrs.to_a
+    end
 
-      attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ")
-      vals = attrs_array.map { |t| t[1] }
-      rows = TopicUser.update_all([attrs_sql, *vals], topic_id: topic_id.to_i, user_id: user_id)
+    # Find the information specific to a user in a forum topic
+    def lookup_for(user, topics)
+      # If the user isn't logged in, there's no last read posts
+      return {} if user.blank? || topics.blank?
 
-      if rows == 0
-        now = DateTime.now
-        auto_track_after = self.exec_sql("select auto_track_topics_after_msecs from users where id = ?", user_id).values[0][0]
-        auto_track_after ||= SiteSetting.auto_track_topics_after
-        auto_track_after = auto_track_after.to_i
+      topic_ids = topics.map(&:id)
+      create_lookup(TopicUser.where(topic_id: topic_ids, user_id: user.id))
+    end
 
-        if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed] || 0)
-          attrs[:notification_level] ||= TopicUser::NotificationLevel::TRACKING
+    def create_lookup(topic_users)
+      topic_users = topic_users.to_a
+
+      result = {}
+      return result if topic_users.blank?
+
+      topic_users.each do |ftu|
+        result[ftu.topic_id] = ftu
+      end
+      result
+    end
+
+    def get(topic,user)
+      topic = topic.id if Topic === topic
+      user = user.id if User === user
+      TopicUser.where('topic_id = ? and user_id = ?', topic, user).first
+    end
+
+    # Change attributes for a user (creates a record when none is present). First it tries an update
+    # since there's more likely to be an existing record than not. If the update returns 0 rows affected
+    # it then creates the row instead.
+    def change(user_id, topic_id, attrs)
+      # Sometimes people pass objs instead of the ids. We can handle that.
+      topic_id = topic_id.id if topic_id.is_a?(Topic)
+      user_id = user_id.id if user_id.is_a?(User)
+
+      TopicUser.transaction do
+        attrs = attrs.dup
+        attrs[:starred_at] = DateTime.now if attrs[:starred_at].nil? && attrs[:starred]
+
+        if attrs[:notification_level]
+          attrs[:notifications_changed_at] ||= DateTime.now
+          attrs[:notifications_reason_id] ||= TopicUser.notification_reasons[:user_changed]
         end
+        attrs_array = attrs.to_a
 
-        TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id.to_i, first_visited_at: now ,last_visited_at: now))
+        attrs_sql = attrs_array.map { |t| "#{t[0]} = ?" }.join(", ")
+        vals = attrs_array.map { |t| t[1] }
+        rows = TopicUser.update_all([attrs_sql, *vals], topic_id: topic_id.to_i, user_id: user_id)
+
+        if rows == 0
+          now = DateTime.now
+          auto_track_after = User.select(:auto_track_topics_after_msecs).where(id: user_id).first.auto_track_topics_after_msecs
+          auto_track_after ||= SiteSetting.auto_track_topics_after
+
+          if auto_track_after >= 0 && auto_track_after <= (attrs[:total_msecs_viewed] || 0)
+            attrs[:notification_level] ||= notification_levels[:tracking]
+          end
+
+          TopicUser.create(attrs.merge!(user_id: user_id, topic_id: topic_id.to_i, first_visited_at: now ,last_visited_at: now))
+        end
       end
+    rescue ActiveRecord::RecordNotUnique
+      # In case of a race condition to insert, do nothing
     end
-  rescue ActiveRecord::RecordNotUnique
-    # In case of a race condition to insert, do nothing
-  end
 
-  def self.track_visit!(topic,user)
-    now = DateTime.now
-    rows = exec_sql_row_count(
-      "update topic_users set last_visited_at=? where topic_id=? and user_id=?",
-      now, topic.id, user.id
-    )
-
-    if rows == 0
-      exec_sql('insert into topic_users(topic_id, user_id, last_visited_at, first_visited_at)
-               values(?,?,?,?)',
-               topic.id, user.id, now, now)
-    end
-  end
-
-  # Update the last read and the last seen post count, but only if it doesn't exist.
-  # This would be a lot easier if psql supported some kind of upsert
-  def self.update_last_read(user, topic_id, post_number, msecs)
-    return if post_number.blank?
-    msecs = 0 if msecs.to_i < 0
-
-    args = {
-      user_id: user.id,
-      topic_id: topic_id,
-      post_number: post_number,
-      now: DateTime.now,
-      msecs: msecs,
-      tracking: TopicUser::NotificationLevel::TRACKING,
-      threshold: SiteSetting.auto_track_topics_after
-    }
-
-    rows = exec_sql("UPDATE topic_users
-                                  SET
-                                    last_read_post_number = greatest(:post_number, tu.last_read_post_number),
-                                    seen_post_count = t.highest_post_number,
-                                    total_msecs_viewed = tu.total_msecs_viewed + :msecs,
-                                    notification_level =
-                                       case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
-                                          coalesce(u.auto_track_topics_after_msecs,:threshold) and
-                                          coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
-                                            :tracking
-                                       else
-                                          tu.notification_level
-                                       end
-                                FROM topic_users tu
-                                join topics t on t.id = tu.topic_id
-                                join users u on u.id = :user_id
-                                WHERE
-                                     tu.topic_id = topic_users.topic_id AND
-                                     tu.user_id = topic_users.user_id AND
-                                     tu.topic_id = :topic_id AND
-                                     tu.user_id = :user_id
-                                RETURNING
-                                  topic_users.notification_level, tu.notification_level old_level
-                              ",
-                              args).values
-
-    if rows.length == 1
-      before = rows[0][1].to_i
-      after = rows[0][0].to_i
-
-      if before != after
-        MessageBus.publish("/topic/#{topic_id}", {notification_level_change: after}, user_ids: [user.id])
+    def track_visit!(topic,user)
+      now = DateTime.now
+      rows = TopicUser.update_all({last_visited_at: now}, {topic_id: topic.id, user_id: user.id})
+      if rows == 0
+        TopicUser.create(topic_id: topic.id, user_id: user.id, last_visited_at: now, first_visited_at: now)
       end
     end
 
-    if rows.length == 0
-      args[:tracking] = TopicUser::NotificationLevel::TRACKING
-      args[:regular] = TopicUser::NotificationLevel::REGULAR
-      args[:site_setting] = SiteSetting.auto_track_topics_after
-      exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
-                SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now,
-                  case when coalesce(u.auto_track_topics_after_msecs, :site_setting) = 0 then :tracking else :regular end
-                FROM topics AS ft
-                JOIN users u on u.id = :user_id
-                WHERE ft.id = :topic_id
-                  AND NOT EXISTS(SELECT 1
-                                 FROM topic_users AS ftu
-                                 WHERE ftu.user_id = :user_id and ftu.topic_id = :topic_id)",
-                args)
+    # Update the last read and the last seen post count, but only if it doesn't exist.
+    # This would be a lot easier if psql supported some kind of upsert
+    def update_last_read(user, topic_id, post_number, msecs)
+      return if post_number.blank?
+      msecs = 0 if msecs.to_i < 0
+
+      args = {
+        user_id: user.id,
+        topic_id: topic_id,
+        post_number: post_number,
+        now: DateTime.now,
+        msecs: msecs,
+        tracking: notification_levels[:tracking],
+        threshold: SiteSetting.auto_track_topics_after
+      }
+
+      rows = exec_sql("UPDATE topic_users
+                                    SET
+                                      last_read_post_number = greatest(:post_number, tu.last_read_post_number),
+                                      seen_post_count = t.highest_post_number,
+                                      total_msecs_viewed = tu.total_msecs_viewed + :msecs,
+                                      notification_level =
+                                         case when tu.notifications_reason_id is null and (tu.total_msecs_viewed + :msecs) >
+                                            coalesce(u.auto_track_topics_after_msecs,:threshold) and
+                                            coalesce(u.auto_track_topics_after_msecs, :threshold) >= 0 then
+                                              :tracking
+                                         else
+                                            tu.notification_level
+                                         end
+                                  FROM topic_users tu
+                                  join topics t on t.id = tu.topic_id
+                                  join users u on u.id = :user_id
+                                  WHERE
+                                       tu.topic_id = topic_users.topic_id AND
+                                       tu.user_id = topic_users.user_id AND
+                                       tu.topic_id = :topic_id AND
+                                       tu.user_id = :user_id
+                                  RETURNING
+                                    topic_users.notification_level, tu.notification_level old_level
+                                ",
+                                args).values
+
+      if rows.length == 1
+        before = rows[0][1].to_i
+        after = rows[0][0].to_i
+
+        if before != after
+          MessageBus.publish("/topic/#{topic_id}", {notification_level_change: after}, user_ids: [user.id])
+        end
+      end
+
+      if rows.length == 0
+        args[:tracking] = notification_levels[:tracking]
+        args[:regular] = notification_levels[:regular]
+        args[:site_setting] = SiteSetting.auto_track_topics_after
+        exec_sql("INSERT INTO topic_users (user_id, topic_id, last_read_post_number, seen_post_count, last_visited_at, first_visited_at, notification_level)
+                  SELECT :user_id, :topic_id, :post_number, ft.highest_post_number, :now, :now,
+                    case when coalesce(u.auto_track_topics_after_msecs, :site_setting) = 0 then :tracking else :regular end
+                  FROM topics AS ft
+                  JOIN users u on u.id = :user_id
+                  WHERE ft.id = :topic_id
+                    AND NOT EXISTS(SELECT 1
+                                   FROM topic_users AS ftu
+                                   WHERE ftu.user_id = :user_id and ftu.topic_id = :topic_id)",
+                  args)
+      end
     end
+
   end
+
 end
diff --git a/app/models/user.rb b/app/models/user.rb
index e14b0916b..33b703a1c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -443,11 +443,8 @@ class User < ActiveRecord::Base
   end
 
   def readable_name
-    if name.present? && name != username
-      "#{name} (#{username})"
-    else
-      username
-    end
+    return "#{name} (#{username})" if name.present? && name != username
+    username
   end
 
   protected
@@ -461,25 +458,14 @@ class User < ActiveRecord::Base
     end
 
     def update_tracked_topics
-      if auto_track_topics_after_msecs_changed?
+      return unless auto_track_topics_after_msecs_changed?
 
-        if auto_track_topics_after_msecs < 0
-
-          User.exec_sql('update topic_users set notification_level = ?
-                         where notifications_reason_id is null and
-                           user_id = ?' , TopicUser::NotificationLevel::REGULAR , id)
-        else
-
-          User.exec_sql('update topic_users set notification_level = ?
-                         where notifications_reason_id is null and
-                           user_id = ? and
-                           total_msecs_viewed < ?' , TopicUser::NotificationLevel::REGULAR , id, auto_track_topics_after_msecs)
-
-          User.exec_sql('update topic_users set notification_level = ?
-                         where notifications_reason_id is null and
-                           user_id = ? and
-                           total_msecs_viewed >= ?' , TopicUser::NotificationLevel::TRACKING , id, auto_track_topics_after_msecs)
-        end
+      where_conditions = {notifications_reason_id: nil, user_id: id}
+      if auto_track_topics_after_msecs < 0
+        TopicUser.update_all({notification_level: TopicUser.notification_levels[:regular]}, where_conditions)
+      else
+        TopicUser.update_all(["notification_level = CASE WHEN total_msecs_viewed < ? THEN ? ELSE ? END",
+                              auto_track_topics_after_msecs, TopicUser.notification_levels[:regular], TopicUser.notification_levels[:tracking]], where_conditions)
       end
     end
 
diff --git a/app/serializers/category_topic_serializer.rb b/app/serializers/category_topic_serializer.rb
index d17fc548a..0df31095c 100644
--- a/app/serializers/category_topic_serializer.rb
+++ b/app/serializers/category_topic_serializer.rb
@@ -2,7 +2,6 @@ class CategoryTopicSerializer < BasicTopicSerializer
 
   attributes :slug,
              :visible,
-             :pinned,
              :closed,
              :archived
 
diff --git a/app/serializers/topic_list_item_serializer.rb b/app/serializers/topic_list_item_serializer.rb
index 49ea8ebaf..e5466abf0 100644
--- a/app/serializers/topic_list_item_serializer.rb
+++ b/app/serializers/topic_list_item_serializer.rb
@@ -1,3 +1,5 @@
+require_dependency 'pinned_check'
+
 class TopicListItemSerializer < BasicTopicSerializer
 
   attributes :views,
@@ -29,4 +31,8 @@ class TopicListItemSerializer < BasicTopicSerializer
     object.posters || []
   end
 
+  def pinned
+    PinnedCheck.new(object, object.user_data).pinned?
+  end
+
 end
diff --git a/app/serializers/topic_view_serializer.rb b/app/serializers/topic_view_serializer.rb
index 337dd11ff..9e7fa4078 100644
--- a/app/serializers/topic_view_serializer.rb
+++ b/app/serializers/topic_view_serializer.rb
@@ -1,3 +1,5 @@
+require_dependency 'pinned_check'
+
 class TopicViewSerializer < ApplicationSerializer
 
   # These attributes will be delegated to the topic
@@ -12,7 +14,6 @@ class TopicViewSerializer < ApplicationSerializer
      :last_posted_at,
      :visible,
      :closed,
-     :pinned,
      :archived,
      :moderator_posts_count,
      :has_best_of,
@@ -42,7 +43,8 @@ class TopicViewSerializer < ApplicationSerializer
              :notifications_reason_id,
              :posts,
              :at_bottom,
-             :highest_post_number
+             :highest_post_number,
+             :pinned
 
   has_one :created_by, serializer: BasicUserSerializer, embed: :objects
   has_one :last_poster, serializer: BasicUserSerializer, embed: :objects
@@ -193,6 +195,10 @@ class TopicViewSerializer < ApplicationSerializer
     object.highest_post_number
   end
 
+  def pinned
+    PinnedCheck.new(object.topic, object.topic_user).pinned?
+  end
+
   def posts
     return @posts if @posts.present?
     @posts = []
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index ff27b03c3..102d0970a 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -452,6 +452,10 @@ en:
         title: 'Reply'
         help: 'begin composing a reply to this topic'
 
+      clear_pin:
+        title: "Clear pin"
+        help: "Clear the pinned status of this topic so it no longer appears at the top of your topic list"
+
       share:
         title: 'Share'
         help: 'share a link to this topic'
diff --git a/config/routes.rb b/config/routes.rb
index 3693bf644..eb6271a44 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -175,6 +175,7 @@ Discourse::Application.routes.draw do
   put 't/:topic_id/star' => 'topics#star', :constraints => {:topic_id => /\d+/}
   put 't/:slug/:topic_id/status' => 'topics#status', :constraints => {:topic_id => /\d+/}
   put 't/:topic_id/status' => 'topics#status', :constraints => {:topic_id => /\d+/}
+  put 't/:topic_id/clear-pin' => 'topics#clear_pin', :constraints => {:topic_id => /\d+/}
   put 't/:topic_id/mute' => 'topics#mute', :constraints => {:topic_id => /\d+/}
   put 't/:topic_id/unmute' => 'topics#unmute', :constraints => {:topic_id => /\d+/}
 
diff --git a/db/migrate/20130306180148_add_cleared_pinned_to_topic_users.rb b/db/migrate/20130306180148_add_cleared_pinned_to_topic_users.rb
new file mode 100644
index 000000000..9cc4af5d9
--- /dev/null
+++ b/db/migrate/20130306180148_add_cleared_pinned_to_topic_users.rb
@@ -0,0 +1,9 @@
+class AddClearedPinnedToTopicUsers < ActiveRecord::Migration
+  def change
+    add_column :topic_users, :cleared_pinned_at, :datetime, null: true
+
+    add_column :topics, :pinned_at, :datetime, null: true
+    execute "UPDATE topics SET pinned_at = created_at WHERE pinned"
+    remove_column :topics, :pinned
+  end
+end
diff --git a/db/structure.sql b/db/structure.sql
index fa70f2c10..09a4e77d2 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -8,6 +8,13 @@ SET standard_conforming_strings = on;
 SET check_function_bodies = false;
 SET client_min_messages = warning;
 
+--
+-- Name: backup; Type: SCHEMA; Schema: -; Owner: -
+--
+
+CREATE SCHEMA backup;
+
+
 --
 -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -
 --
@@ -36,12 +43,1176 @@ CREATE EXTENSION IF NOT EXISTS hstore WITH SCHEMA public;
 COMMENT ON EXTENSION hstore IS 'data type for storing sets of (key, value) pairs';
 
 
-SET search_path = public, pg_catalog;
+SET search_path = backup, pg_catalog;
 
 SET default_tablespace = '';
 
 SET default_with_oids = false;
 
+--
+-- Name: categories; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE categories (
+    id integer NOT NULL,
+    name character varying(50) NOT NULL,
+    color character varying(6) DEFAULT 'AB9364'::character varying NOT NULL,
+    topic_id integer,
+    top1_topic_id integer,
+    top2_topic_id integer,
+    top1_user_id integer,
+    top2_user_id integer,
+    topic_count integer DEFAULT 0 NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    user_id integer NOT NULL,
+    topics_year integer,
+    topics_month integer,
+    topics_week integer,
+    slug character varying(255) NOT NULL
+);
+
+
+--
+-- Name: categories_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE categories_id_seq
+    START WITH 5
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: categories_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE categories_id_seq OWNED BY categories.id;
+
+
+--
+-- Name: category_featured_topics; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE category_featured_topics (
+    category_id integer NOT NULL,
+    topic_id integer NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: category_featured_users; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE category_featured_users (
+    id integer NOT NULL,
+    category_id integer,
+    user_id integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: category_featured_users_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE category_featured_users_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: category_featured_users_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE category_featured_users_id_seq OWNED BY category_featured_users.id;
+
+
+--
+-- Name: draft_sequences; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE draft_sequences (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    draft_key character varying(255) NOT NULL,
+    sequence integer NOT NULL
+);
+
+
+--
+-- Name: draft_sequences_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE draft_sequences_id_seq
+    START WITH 20
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: draft_sequences_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE draft_sequences_id_seq OWNED BY draft_sequences.id;
+
+
+--
+-- Name: drafts; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE drafts (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    draft_key character varying(255) NOT NULL,
+    data text NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    sequence integer DEFAULT 0 NOT NULL
+);
+
+
+--
+-- Name: drafts_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE drafts_id_seq
+    START WITH 2
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: drafts_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE drafts_id_seq OWNED BY drafts.id;
+
+
+--
+-- Name: email_logs; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE email_logs (
+    id integer NOT NULL,
+    to_address character varying(255) NOT NULL,
+    email_type character varying(255) NOT NULL,
+    user_id integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: email_logs_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE email_logs_id_seq
+    START WITH 3
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: email_logs_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE email_logs_id_seq OWNED BY email_logs.id;
+
+
+--
+-- Name: email_tokens; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE email_tokens (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    email character varying(255) NOT NULL,
+    token character varying(255) NOT NULL,
+    confirmed boolean DEFAULT false NOT NULL,
+    expired boolean DEFAULT false NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: email_tokens_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE email_tokens_id_seq
+    START WITH 3
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: email_tokens_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE email_tokens_id_seq OWNED BY email_tokens.id;
+
+
+--
+-- Name: facebook_user_infos; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE facebook_user_infos (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    facebook_user_id integer NOT NULL,
+    username character varying(255) NOT NULL,
+    first_name character varying(255),
+    last_name character varying(255),
+    email character varying(255),
+    gender character varying(255),
+    name character varying(255),
+    link character varying(255),
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: facebook_user_infos_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE facebook_user_infos_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: facebook_user_infos_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE facebook_user_infos_id_seq OWNED BY facebook_user_infos.id;
+
+
+--
+-- Name: incoming_links; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE incoming_links (
+    id integer NOT NULL,
+    url character varying(1000) NOT NULL,
+    referer character varying(1000) NOT NULL,
+    domain character varying(100) NOT NULL,
+    topic_id integer,
+    post_number integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: incoming_links_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE incoming_links_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: incoming_links_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE incoming_links_id_seq OWNED BY incoming_links.id;
+
+
+--
+-- Name: invites; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE invites (
+    id integer NOT NULL,
+    invite_key character varying(32) NOT NULL,
+    email character varying(255) NOT NULL,
+    invited_by_id integer NOT NULL,
+    user_id integer,
+    redeemed_at timestamp without time zone,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    deleted_at timestamp without time zone
+);
+
+
+--
+-- Name: invites_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE invites_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: invites_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE invites_id_seq OWNED BY invites.id;
+
+
+--
+-- Name: notifications; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE notifications (
+    id integer NOT NULL,
+    notification_type integer NOT NULL,
+    user_id integer NOT NULL,
+    data character varying(255) NOT NULL,
+    read boolean DEFAULT false NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    topic_id integer,
+    post_number integer,
+    post_action_id integer
+);
+
+
+--
+-- Name: notifications_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE notifications_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: notifications_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE notifications_id_seq OWNED BY notifications.id;
+
+
+--
+-- Name: onebox_renders; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE onebox_renders (
+    id integer NOT NULL,
+    url character varying(255) NOT NULL,
+    cooked text NOT NULL,
+    expires_at timestamp without time zone NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    preview text
+);
+
+
+--
+-- Name: onebox_renders_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE onebox_renders_id_seq
+    START WITH 2
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: onebox_renders_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE onebox_renders_id_seq OWNED BY onebox_renders.id;
+
+
+--
+-- Name: post_action_types; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE post_action_types (
+    name_key character varying(50) NOT NULL,
+    is_flag boolean DEFAULT false NOT NULL,
+    icon character varying(20),
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    id integer NOT NULL
+);
+
+
+--
+-- Name: post_action_types_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE post_action_types_id_seq
+    START WITH 6
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: post_action_types_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE post_action_types_id_seq OWNED BY post_action_types.id;
+
+
+--
+-- Name: post_actions; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE post_actions (
+    id integer NOT NULL,
+    post_id integer NOT NULL,
+    user_id integer NOT NULL,
+    post_action_type_id integer NOT NULL,
+    deleted_at timestamp without time zone,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: post_actions_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE post_actions_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: post_actions_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE post_actions_id_seq OWNED BY post_actions.id;
+
+
+--
+-- Name: post_onebox_renders; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE post_onebox_renders (
+    post_id integer NOT NULL,
+    onebox_render_id integer NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: post_replies; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE post_replies (
+    post_id integer,
+    reply_id integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: post_timings; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE post_timings (
+    topic_id integer NOT NULL,
+    post_number integer NOT NULL,
+    user_id integer NOT NULL,
+    msecs integer NOT NULL
+);
+
+
+--
+-- Name: posts; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE posts (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    topic_id integer NOT NULL,
+    post_number integer NOT NULL,
+    raw text NOT NULL,
+    cooked text NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    reply_to_post_number integer,
+    cached_version integer DEFAULT 1 NOT NULL,
+    reply_count integer DEFAULT 0 NOT NULL,
+    quote_count integer DEFAULT 0 NOT NULL,
+    reply_below_post_number integer,
+    deleted_at timestamp without time zone,
+    off_topic_count integer DEFAULT 0 NOT NULL,
+    offensive_count integer DEFAULT 0 NOT NULL,
+    like_count integer DEFAULT 0 NOT NULL,
+    incoming_link_count integer DEFAULT 0 NOT NULL,
+    bookmark_count integer DEFAULT 0 NOT NULL,
+    avg_time integer,
+    score double precision,
+    reads integer DEFAULT 0 NOT NULL,
+    post_type integer DEFAULT 1 NOT NULL,
+    vote_count integer DEFAULT 0 NOT NULL,
+    sort_order integer,
+    last_editor_id integer
+);
+
+
+--
+-- Name: posts_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE posts_id_seq
+    START WITH 16
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: posts_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE posts_id_seq OWNED BY posts.id;
+
+
+--
+-- Name: site_customizations; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE site_customizations (
+    id integer NOT NULL,
+    name character varying(255) NOT NULL,
+    stylesheet text,
+    header text,
+    "position" integer NOT NULL,
+    user_id integer NOT NULL,
+    enabled boolean NOT NULL,
+    key character varying(255) NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    override_default_style boolean DEFAULT false NOT NULL,
+    stylesheet_baked text DEFAULT ''::text NOT NULL
+);
+
+
+--
+-- Name: site_customizations_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE site_customizations_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: site_customizations_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE site_customizations_id_seq OWNED BY site_customizations.id;
+
+
+--
+-- Name: site_settings; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE site_settings (
+    id integer NOT NULL,
+    name character varying(255) NOT NULL,
+    data_type integer NOT NULL,
+    value text,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: site_settings_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE site_settings_id_seq
+    START WITH 4
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: site_settings_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE site_settings_id_seq OWNED BY site_settings.id;
+
+
+--
+-- Name: topic_allowed_users; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topic_allowed_users (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    topic_id integer NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: topic_allowed_users_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE topic_allowed_users_id_seq
+    START WITH 3
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: topic_allowed_users_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE topic_allowed_users_id_seq OWNED BY topic_allowed_users.id;
+
+
+--
+-- Name: topic_invites; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topic_invites (
+    id integer NOT NULL,
+    topic_id integer NOT NULL,
+    invite_id integer NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: topic_invites_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE topic_invites_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: topic_invites_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE topic_invites_id_seq OWNED BY topic_invites.id;
+
+
+--
+-- Name: topic_link_clicks; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topic_link_clicks (
+    id integer NOT NULL,
+    topic_link_id integer NOT NULL,
+    user_id integer,
+    ip bigint NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: topic_link_clicks_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE topic_link_clicks_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: topic_link_clicks_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE topic_link_clicks_id_seq OWNED BY topic_link_clicks.id;
+
+
+--
+-- Name: topic_links; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topic_links (
+    id integer NOT NULL,
+    topic_id integer NOT NULL,
+    post_id integer,
+    user_id integer NOT NULL,
+    url character varying(500) NOT NULL,
+    domain character varying(100) NOT NULL,
+    internal boolean DEFAULT false NOT NULL,
+    link_topic_id integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    reflection boolean DEFAULT false,
+    clicks integer DEFAULT 0 NOT NULL,
+    link_post_id integer
+);
+
+
+--
+-- Name: topic_links_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE topic_links_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: topic_links_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE topic_links_id_seq OWNED BY topic_links.id;
+
+
+--
+-- Name: topic_users; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topic_users (
+    user_id integer NOT NULL,
+    topic_id integer NOT NULL,
+    starred boolean DEFAULT false NOT NULL,
+    posted boolean DEFAULT false NOT NULL,
+    last_read_post_number integer,
+    seen_post_count integer,
+    starred_at timestamp without time zone,
+    muted_at timestamp without time zone,
+    last_visited_at timestamp without time zone,
+    first_visited_at timestamp without time zone,
+    notifications integer DEFAULT 2,
+    notifications_changed_at timestamp without time zone,
+    notifications_reason_id integer,
+    CONSTRAINT test_starred_at CHECK (((starred = false) OR (starred_at IS NOT NULL)))
+);
+
+
+--
+-- Name: topics; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE topics (
+    id integer NOT NULL,
+    title character varying(255) NOT NULL,
+    last_posted_at timestamp without time zone,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    views integer DEFAULT 0 NOT NULL,
+    posts_count integer DEFAULT 0 NOT NULL,
+    user_id integer NOT NULL,
+    last_post_user_id integer NOT NULL,
+    reply_count integer DEFAULT 0 NOT NULL,
+    featured_user1_id integer,
+    featured_user2_id integer,
+    featured_user3_id integer,
+    avg_time integer,
+    deleted_at timestamp without time zone,
+    highest_post_number integer DEFAULT 0 NOT NULL,
+    image_url character varying(255),
+    off_topic_count integer DEFAULT 0 NOT NULL,
+    offensive_count integer DEFAULT 0 NOT NULL,
+    like_count integer DEFAULT 0 NOT NULL,
+    incoming_link_count integer DEFAULT 0 NOT NULL,
+    bookmark_count integer DEFAULT 0 NOT NULL,
+    star_count integer DEFAULT 0 NOT NULL,
+    category_id integer,
+    visible boolean DEFAULT true NOT NULL,
+    moderator_posts_count integer DEFAULT 0 NOT NULL,
+    closed boolean DEFAULT false NOT NULL,
+    pinned boolean DEFAULT false NOT NULL,
+    archived boolean DEFAULT false NOT NULL,
+    bumped_at timestamp without time zone NOT NULL,
+    sub_tag character varying(255),
+    has_best_of boolean DEFAULT false NOT NULL,
+    meta_data public.hstore,
+    vote_count integer DEFAULT 0 NOT NULL,
+    archetype character varying(255) DEFAULT 'regular'::character varying NOT NULL,
+    featured_user4_id integer
+);
+
+
+--
+-- Name: topics_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE topics_id_seq
+    START WITH 16
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: topics_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE topics_id_seq OWNED BY topics.id;
+
+
+--
+-- Name: trust_levels; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE trust_levels (
+    id integer NOT NULL,
+    name_key character varying(255) NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: trust_levels_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE trust_levels_id_seq
+    START WITH 3
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: trust_levels_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE trust_levels_id_seq OWNED BY trust_levels.id;
+
+
+--
+-- Name: twitter_user_infos; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE twitter_user_infos (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    screen_name character varying(255) NOT NULL,
+    twitter_user_id integer NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: twitter_user_infos_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE twitter_user_infos_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: twitter_user_infos_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE twitter_user_infos_id_seq OWNED BY twitter_user_infos.id;
+
+
+--
+-- Name: uploads; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE uploads (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    topic_id integer NOT NULL,
+    original_filename character varying(255) NOT NULL,
+    filesize integer NOT NULL,
+    width integer,
+    height integer,
+    url character varying(255) NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: uploads_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE uploads_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: uploads_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE uploads_id_seq OWNED BY uploads.id;
+
+
+--
+-- Name: user_actions; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_actions (
+    id integer NOT NULL,
+    action_type integer NOT NULL,
+    user_id integer NOT NULL,
+    target_topic_id integer,
+    target_post_id integer,
+    target_user_id integer,
+    acting_user_id integer,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: user_actions_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE user_actions_id_seq
+    START WITH 40
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: user_actions_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE user_actions_id_seq OWNED BY user_actions.id;
+
+
+--
+-- Name: user_open_ids; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_open_ids (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    email character varying(255) NOT NULL,
+    url character varying(255) NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    active boolean NOT NULL
+);
+
+
+--
+-- Name: user_open_ids_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE user_open_ids_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: user_open_ids_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE user_open_ids_id_seq OWNED BY user_open_ids.id;
+
+
+--
+-- Name: user_visits; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE user_visits (
+    id integer NOT NULL,
+    user_id integer NOT NULL,
+    visited_at date NOT NULL
+);
+
+
+--
+-- Name: user_visits_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE user_visits_id_seq
+    START WITH 4
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: user_visits_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE user_visits_id_seq OWNED BY user_visits.id;
+
+
+--
+-- Name: users; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE users (
+    id integer NOT NULL,
+    username character varying(20) NOT NULL,
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL,
+    name character varying(255),
+    bio_raw text,
+    seen_notification_id integer DEFAULT 0 NOT NULL,
+    last_posted_at timestamp without time zone,
+    email character varying(256) NOT NULL,
+    password_hash character varying(64),
+    salt character varying(32),
+    active boolean,
+    username_lower character varying(20) NOT NULL,
+    auth_token character varying(32),
+    last_seen_at timestamp without time zone,
+    website character varying(255),
+    admin boolean DEFAULT false NOT NULL,
+    moderator boolean DEFAULT false NOT NULL,
+    last_emailed_at timestamp without time zone,
+    email_digests boolean DEFAULT true NOT NULL,
+    trust_level_id integer DEFAULT 1 NOT NULL,
+    bio_cooked text,
+    email_private_messages boolean DEFAULT true,
+    email_direct boolean DEFAULT true NOT NULL,
+    approved boolean DEFAULT false NOT NULL,
+    approved_by_id integer,
+    approved_at timestamp without time zone,
+    topics_entered integer DEFAULT 0 NOT NULL,
+    posts_read_count integer DEFAULT 0 NOT NULL,
+    digest_after_days integer DEFAULT 7 NOT NULL,
+    previous_visit_at timestamp without time zone
+);
+
+
+--
+-- Name: users_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE users_id_seq
+    START WITH 3
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: users_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE users_id_seq OWNED BY users.id;
+
+
+--
+-- Name: versions; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE versions (
+    id integer NOT NULL,
+    versioned_id integer,
+    versioned_type character varying(255),
+    user_id integer,
+    user_type character varying(255),
+    user_name character varying(255),
+    modifications text,
+    number integer,
+    reverted_from integer,
+    tag character varying(255),
+    created_at timestamp without time zone NOT NULL,
+    updated_at timestamp without time zone NOT NULL
+);
+
+
+--
+-- Name: versions_id_seq; Type: SEQUENCE; Schema: backup; Owner: -
+--
+
+CREATE SEQUENCE versions_id_seq
+    START WITH 1
+    INCREMENT BY 1
+    NO MINVALUE
+    NO MAXVALUE
+    CACHE 1;
+
+
+--
+-- Name: versions_id_seq; Type: SEQUENCE OWNED BY; Schema: backup; Owner: -
+--
+
+ALTER SEQUENCE versions_id_seq OWNED BY versions.id;
+
+
+--
+-- Name: views; Type: TABLE; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE TABLE views (
+    parent_id integer NOT NULL,
+    parent_type character varying(50) NOT NULL,
+    ip bigint NOT NULL,
+    viewed_at timestamp without time zone NOT NULL,
+    user_id integer
+);
+
+
+SET search_path = public, pg_catalog;
+
 --
 -- Name: categories; Type: TABLE; Schema: public; Owner: -; Tablespace: 
 --
@@ -926,6 +2097,7 @@ CREATE TABLE topic_users (
     notifications_changed_at timestamp without time zone,
     notifications_reason_id integer,
     total_msecs_viewed integer DEFAULT 0 NOT NULL,
+    cleared_pinned_at timestamp without time zone,
     CONSTRAINT test_starred_at CHECK (((starred = false) OR (starred_at IS NOT NULL)))
 );
 
@@ -961,7 +2133,6 @@ CREATE TABLE topics (
     visible boolean DEFAULT true NOT NULL,
     moderator_posts_count integer DEFAULT 0 NOT NULL,
     closed boolean DEFAULT false NOT NULL,
-    pinned boolean DEFAULT false NOT NULL,
     archived boolean DEFAULT false NOT NULL,
     bumped_at timestamp without time zone NOT NULL,
     has_best_of boolean DEFAULT false NOT NULL,
@@ -972,7 +2143,8 @@ CREATE TABLE topics (
     custom_flag_count integer DEFAULT 0 NOT NULL,
     spam_count integer DEFAULT 0 NOT NULL,
     illegal_count integer DEFAULT 0 NOT NULL,
-    inappropriate_count integer DEFAULT 0 NOT NULL
+    inappropriate_count integer DEFAULT 0 NOT NULL,
+    pinned_at timestamp without time zone
 );
 
 
@@ -1294,6 +2466,213 @@ CREATE TABLE views (
 );
 
 
+SET search_path = backup, pg_catalog;
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY categories ALTER COLUMN id SET DEFAULT nextval('categories_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY category_featured_users ALTER COLUMN id SET DEFAULT nextval('category_featured_users_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY draft_sequences ALTER COLUMN id SET DEFAULT nextval('draft_sequences_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY drafts ALTER COLUMN id SET DEFAULT nextval('drafts_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY email_logs ALTER COLUMN id SET DEFAULT nextval('email_logs_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY email_tokens ALTER COLUMN id SET DEFAULT nextval('email_tokens_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY facebook_user_infos ALTER COLUMN id SET DEFAULT nextval('facebook_user_infos_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY incoming_links ALTER COLUMN id SET DEFAULT nextval('incoming_links_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY invites ALTER COLUMN id SET DEFAULT nextval('invites_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY notifications ALTER COLUMN id SET DEFAULT nextval('notifications_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY onebox_renders ALTER COLUMN id SET DEFAULT nextval('onebox_renders_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY post_action_types ALTER COLUMN id SET DEFAULT nextval('post_action_types_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY post_actions ALTER COLUMN id SET DEFAULT nextval('post_actions_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY posts ALTER COLUMN id SET DEFAULT nextval('posts_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY site_customizations ALTER COLUMN id SET DEFAULT nextval('site_customizations_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY site_settings ALTER COLUMN id SET DEFAULT nextval('site_settings_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY topic_allowed_users ALTER COLUMN id SET DEFAULT nextval('topic_allowed_users_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY topic_invites ALTER COLUMN id SET DEFAULT nextval('topic_invites_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY topic_link_clicks ALTER COLUMN id SET DEFAULT nextval('topic_link_clicks_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY topic_links ALTER COLUMN id SET DEFAULT nextval('topic_links_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY topics ALTER COLUMN id SET DEFAULT nextval('topics_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY trust_levels ALTER COLUMN id SET DEFAULT nextval('trust_levels_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY twitter_user_infos ALTER COLUMN id SET DEFAULT nextval('twitter_user_infos_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY uploads ALTER COLUMN id SET DEFAULT nextval('uploads_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY user_actions ALTER COLUMN id SET DEFAULT nextval('user_actions_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY user_open_ids ALTER COLUMN id SET DEFAULT nextval('user_open_ids_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY user_visits ALTER COLUMN id SET DEFAULT nextval('user_visits_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regclass);
+
+
+--
+-- Name: id; Type: DEFAULT; Schema: backup; Owner: -
+--
+
+ALTER TABLE ONLY versions ALTER COLUMN id SET DEFAULT nextval('versions_id_seq'::regclass);
+
+
+SET search_path = public, pg_catalog;
+
 --
 -- Name: id; Type: DEFAULT; Schema: public; Owner: -
 --
@@ -1504,6 +2883,242 @@ ALTER TABLE ONLY users ALTER COLUMN id SET DEFAULT nextval('users_id_seq'::regcl
 ALTER TABLE ONLY versions ALTER COLUMN id SET DEFAULT nextval('versions_id_seq'::regclass);
 
 
+SET search_path = backup, pg_catalog;
+
+--
+-- Name: actions_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_actions
+    ADD CONSTRAINT actions_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: categories_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY categories
+    ADD CONSTRAINT categories_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: category_featured_users_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY category_featured_users
+    ADD CONSTRAINT category_featured_users_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: draft_sequences_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY draft_sequences
+    ADD CONSTRAINT draft_sequences_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: drafts_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY drafts
+    ADD CONSTRAINT drafts_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: email_logs_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY email_logs
+    ADD CONSTRAINT email_logs_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: email_tokens_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY email_tokens
+    ADD CONSTRAINT email_tokens_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: facebook_user_infos_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY facebook_user_infos
+    ADD CONSTRAINT facebook_user_infos_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: forum_thread_link_clicks_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY topic_link_clicks
+    ADD CONSTRAINT forum_thread_link_clicks_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: forum_thread_links_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY topic_links
+    ADD CONSTRAINT forum_thread_links_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: forum_threads_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY topics
+    ADD CONSTRAINT forum_threads_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: incoming_links_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY incoming_links
+    ADD CONSTRAINT incoming_links_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: invites_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY invites
+    ADD CONSTRAINT invites_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: notifications_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY notifications
+    ADD CONSTRAINT notifications_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: onebox_renders_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY onebox_renders
+    ADD CONSTRAINT onebox_renders_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: post_action_types_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY post_action_types
+    ADD CONSTRAINT post_action_types_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: post_actions_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY post_actions
+    ADD CONSTRAINT post_actions_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: posts_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY posts
+    ADD CONSTRAINT posts_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: site_customizations_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site_customizations
+    ADD CONSTRAINT site_customizations_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: site_settings_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY site_settings
+    ADD CONSTRAINT site_settings_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: topic_allowed_users_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY topic_allowed_users
+    ADD CONSTRAINT topic_allowed_users_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: topic_invites_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY topic_invites
+    ADD CONSTRAINT topic_invites_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: trust_levels_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY trust_levels
+    ADD CONSTRAINT trust_levels_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: twitter_user_infos_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY twitter_user_infos
+    ADD CONSTRAINT twitter_user_infos_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: uploads_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY uploads
+    ADD CONSTRAINT uploads_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: user_open_ids_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_open_ids
+    ADD CONSTRAINT user_open_ids_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: user_visits_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY user_visits
+    ADD CONSTRAINT user_visits_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: users_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY users
+    ADD CONSTRAINT users_pkey PRIMARY KEY (id);
+
+
+--
+-- Name: versions_pkey; Type: CONSTRAINT; Schema: backup; Owner: -; Tablespace: 
+--
+
+ALTER TABLE ONLY versions
+    ADD CONSTRAINT versions_pkey PRIMARY KEY (id);
+
+
+SET search_path = public, pg_catalog;
+
 --
 -- Name: actions_pkey; Type: CONSTRAINT; Schema: public; Owner: -; Tablespace: 
 --
@@ -1768,6 +3383,430 @@ ALTER TABLE ONLY versions
     ADD CONSTRAINT versions_pkey PRIMARY KEY (id);
 
 
+SET search_path = backup, pg_catalog;
+
+--
+-- Name: cat_featured_threads; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX cat_featured_threads ON category_featured_topics USING btree (category_id, topic_id);
+
+
+--
+-- Name: idx_search_thread; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX idx_search_thread ON topics USING gin (to_tsvector('english'::regconfig, (title)::text));
+
+
+--
+-- Name: idx_search_user; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX idx_search_user ON users USING gin (to_tsvector('english'::regconfig, (username)::text));
+
+
+--
+-- Name: idx_unique_actions; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX idx_unique_actions ON post_actions USING btree (user_id, post_action_type_id, post_id) WHERE (deleted_at IS NULL);
+
+
+--
+-- Name: idx_unique_rows; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX idx_unique_rows ON user_actions USING btree (action_type, user_id, target_topic_id, target_post_id, acting_user_id);
+
+
+--
+-- Name: incoming_index; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX incoming_index ON incoming_links USING btree (topic_id, post_number);
+
+
+--
+-- Name: index_actions_on_acting_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_actions_on_acting_user_id ON user_actions USING btree (acting_user_id);
+
+
+--
+-- Name: index_actions_on_user_id_and_action_type; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_actions_on_user_id_and_action_type ON user_actions USING btree (user_id, action_type);
+
+
+--
+-- Name: index_categories_on_forum_thread_count; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_categories_on_forum_thread_count ON categories USING btree (topic_count);
+
+
+--
+-- Name: index_categories_on_name; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_categories_on_name ON categories USING btree (name);
+
+
+--
+-- Name: index_category_featured_users_on_category_id_and_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_category_featured_users_on_category_id_and_user_id ON category_featured_users USING btree (category_id, user_id);
+
+
+--
+-- Name: index_draft_sequences_on_user_id_and_draft_key; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_draft_sequences_on_user_id_and_draft_key ON draft_sequences USING btree (user_id, draft_key);
+
+
+--
+-- Name: index_drafts_on_user_id_and_draft_key; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_drafts_on_user_id_and_draft_key ON drafts USING btree (user_id, draft_key);
+
+
+--
+-- Name: index_email_logs_on_created_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_email_logs_on_created_at ON email_logs USING btree (created_at DESC);
+
+
+--
+-- Name: index_email_logs_on_user_id_and_created_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_email_logs_on_user_id_and_created_at ON email_logs USING btree (user_id, created_at DESC);
+
+
+--
+-- Name: index_email_tokens_on_token; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_email_tokens_on_token ON email_tokens USING btree (token);
+
+
+--
+-- Name: index_facebook_user_infos_on_facebook_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_facebook_user_infos_on_facebook_user_id ON facebook_user_infos USING btree (facebook_user_id);
+
+
+--
+-- Name: index_facebook_user_infos_on_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_facebook_user_infos_on_user_id ON facebook_user_infos USING btree (user_id);
+
+
+--
+-- Name: index_forum_thread_link_clicks_on_forum_thread_link_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_forum_thread_link_clicks_on_forum_thread_link_id ON topic_link_clicks USING btree (topic_link_id);
+
+
+--
+-- Name: index_forum_thread_links_on_forum_thread_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_forum_thread_links_on_forum_thread_id ON topic_links USING btree (topic_id);
+
+
+--
+-- Name: index_forum_thread_links_on_forum_thread_id_and_post_id_and_url; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_forum_thread_links_on_forum_thread_id_and_post_id_and_url ON topic_links USING btree (topic_id, post_id, url);
+
+
+--
+-- Name: index_forum_thread_users_on_forum_thread_id_and_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_forum_thread_users_on_forum_thread_id_and_user_id ON topic_users USING btree (topic_id, user_id);
+
+
+--
+-- Name: index_forum_threads_on_bumped_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_forum_threads_on_bumped_at ON topics USING btree (bumped_at DESC);
+
+
+--
+-- Name: index_forum_threads_on_category_id_and_sub_tag_and_bumped_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_forum_threads_on_category_id_and_sub_tag_and_bumped_at ON topics USING btree (category_id, sub_tag, bumped_at);
+
+
+--
+-- Name: index_invites_on_email_and_invited_by_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_invites_on_email_and_invited_by_id ON invites USING btree (email, invited_by_id);
+
+
+--
+-- Name: index_invites_on_invite_key; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_invites_on_invite_key ON invites USING btree (invite_key);
+
+
+--
+-- Name: index_notifications_on_post_action_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_notifications_on_post_action_id ON notifications USING btree (post_action_id);
+
+
+--
+-- Name: index_notifications_on_user_id_and_created_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_notifications_on_user_id_and_created_at ON notifications USING btree (user_id, created_at);
+
+
+--
+-- Name: index_onebox_renders_on_url; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_onebox_renders_on_url ON onebox_renders USING btree (url);
+
+
+--
+-- Name: index_post_actions_on_post_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_post_actions_on_post_id ON post_actions USING btree (post_id);
+
+
+--
+-- Name: index_post_onebox_renders_on_post_id_and_onebox_render_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_post_onebox_renders_on_post_id_and_onebox_render_id ON post_onebox_renders USING btree (post_id, onebox_render_id);
+
+
+--
+-- Name: index_post_replies_on_post_id_and_reply_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_post_replies_on_post_id_and_reply_id ON post_replies USING btree (post_id, reply_id);
+
+
+--
+-- Name: index_posts_on_reply_to_post_number; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_posts_on_reply_to_post_number ON posts USING btree (reply_to_post_number);
+
+
+--
+-- Name: index_posts_on_topic_id_and_post_number; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_posts_on_topic_id_and_post_number ON posts USING btree (topic_id, post_number);
+
+
+--
+-- Name: index_site_customizations_on_key; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_site_customizations_on_key ON site_customizations USING btree (key);
+
+
+--
+-- Name: index_topic_allowed_users_on_topic_id_and_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_topic_allowed_users_on_topic_id_and_user_id ON topic_allowed_users USING btree (topic_id, user_id);
+
+
+--
+-- Name: index_topic_allowed_users_on_user_id_and_topic_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_topic_allowed_users_on_user_id_and_topic_id ON topic_allowed_users USING btree (user_id, topic_id);
+
+
+--
+-- Name: index_topic_invites_on_invite_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_topic_invites_on_invite_id ON topic_invites USING btree (invite_id);
+
+
+--
+-- Name: index_topic_invites_on_topic_id_and_invite_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_topic_invites_on_topic_id_and_invite_id ON topic_invites USING btree (topic_id, invite_id);
+
+
+--
+-- Name: index_twitter_user_infos_on_twitter_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_twitter_user_infos_on_twitter_user_id ON twitter_user_infos USING btree (twitter_user_id);
+
+
+--
+-- Name: index_twitter_user_infos_on_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_twitter_user_infos_on_user_id ON twitter_user_infos USING btree (user_id);
+
+
+--
+-- Name: index_uploads_on_forum_thread_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_uploads_on_forum_thread_id ON uploads USING btree (topic_id);
+
+
+--
+-- Name: index_uploads_on_user_id; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_uploads_on_user_id ON uploads USING btree (user_id);
+
+
+--
+-- Name: index_user_open_ids_on_url; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_user_open_ids_on_url ON user_open_ids USING btree (url);
+
+
+--
+-- Name: index_user_visits_on_user_id_and_visited_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_user_visits_on_user_id_and_visited_at ON user_visits USING btree (user_id, visited_at);
+
+
+--
+-- Name: index_users_on_auth_token; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_users_on_auth_token ON users USING btree (auth_token);
+
+
+--
+-- Name: index_users_on_email; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_users_on_email ON users USING btree (email);
+
+
+--
+-- Name: index_users_on_last_posted_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_users_on_last_posted_at ON users USING btree (last_posted_at);
+
+
+--
+-- Name: index_users_on_username; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_users_on_username ON users USING btree (username);
+
+
+--
+-- Name: index_users_on_username_lower; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX index_users_on_username_lower ON users USING btree (username_lower);
+
+
+--
+-- Name: index_versions_on_created_at; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_created_at ON versions USING btree (created_at);
+
+
+--
+-- Name: index_versions_on_number; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_number ON versions USING btree (number);
+
+
+--
+-- Name: index_versions_on_tag; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_tag ON versions USING btree (tag);
+
+
+--
+-- Name: index_versions_on_user_id_and_user_type; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_user_id_and_user_type ON versions USING btree (user_id, user_type);
+
+
+--
+-- Name: index_versions_on_user_name; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_user_name ON versions USING btree (user_name);
+
+
+--
+-- Name: index_versions_on_versioned_id_and_versioned_type; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_versions_on_versioned_id_and_versioned_type ON versions USING btree (versioned_id, versioned_type);
+
+
+--
+-- Name: index_views_on_parent_id_and_parent_type; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX index_views_on_parent_id_and_parent_type ON views USING btree (parent_id, parent_type);
+
+
+--
+-- Name: post_timings_summary; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE INDEX post_timings_summary ON post_timings USING btree (topic_id, post_number);
+
+
+--
+-- Name: post_timings_unique; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX post_timings_unique ON post_timings USING btree (topic_id, post_number, user_id);
+
+
+--
+-- Name: unique_views; Type: INDEX; Schema: backup; Owner: -; Tablespace: 
+--
+
+CREATE UNIQUE INDEX unique_views ON views USING btree (parent_id, parent_type, ip, viewed_at);
+
+
+SET search_path = public, pg_catalog;
+
 --
 -- Name: cat_featured_threads; Type: INDEX; Schema: public; Owner: -; Tablespace: 
 --
@@ -2603,4 +4642,6 @@ INSERT INTO schema_migrations (version) VALUES ('20130213203300');
 
 INSERT INTO schema_migrations (version) VALUES ('20130221215017');
 
-INSERT INTO schema_migrations (version) VALUES ('20130226015336');
\ No newline at end of file
+INSERT INTO schema_migrations (version) VALUES ('20130226015336');
+
+INSERT INTO schema_migrations (version) VALUES ('20130306180148');
\ No newline at end of file
diff --git a/lib/pinned_check.rb b/lib/pinned_check.rb
new file mode 100644
index 000000000..d1890c58b
--- /dev/null
+++ b/lib/pinned_check.rb
@@ -0,0 +1,24 @@
+# Helps us determine whether a topic should be displayed as pinned or not,
+# taking into account anonymous users and users who have dismissed it
+class PinnedCheck
+
+  def initialize(topic, topic_user=nil)
+    @topic, @topic_user = topic, topic_user
+  end
+
+  def pinned?
+
+    # If the topic isn't pinned the answer is false
+    return false if @topic.pinned_at.blank?
+
+    # If the user is anonymous or hasn't entered the topic, the value is always true
+    return true if @topic_user.blank?
+
+    # If the user hasn't cleared the pin, it's true
+    return true if @topic_user.cleared_pinned_at.blank?
+
+    # The final check is to see whether the cleared the pin before or after it was last pinned
+    @topic_user.cleared_pinned_at < @topic.pinned_at
+  end
+
+end
\ No newline at end of file
diff --git a/lib/topic_query.rb b/lib/topic_query.rb
index 468f975ee..f6a75dcaa 100644
--- a/lib/topic_query.rb
+++ b/lib/topic_query.rb
@@ -6,6 +6,47 @@ require_dependency 'topic_list'
 
 class TopicQuery
 
+  class << self
+    # use the constants in conjuction with COALESCE to determine the order with regard to pinned
+    # topics that have been cleared by the user. There
+    # might be a cleaner way to do this.
+    def lowest_date
+      "2010-01-01"
+    end
+
+    def highest_date
+      "3000-01-01"
+    end
+
+    # If you've clearned the pin, use bumped_at, otherwise put it at the top
+    def order_with_pinned_sql
+      "CASE
+        WHEN (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
+          THEN '#{highest_date}'
+        ELSE topics.bumped_at
+       END DESC"
+    end
+
+    # If you've clearned the pin, use bumped_at, otherwise put it at the top
+    def order_nocategory_with_pinned_sql
+      "CASE
+        WHEN topics.category_id IS NULL and (COALESCE(topics.pinned_at, '#{lowest_date}') > COALESCE(tu.cleared_pinned_at, '#{lowest_date}'))
+          THEN '#{highest_date}'
+        ELSE topics.bumped_at
+       END DESC"
+    end
+
+    # For anonymous users
+    def order_nocategory_basic_bumped
+      "CASE WHEN topics.category_id IS NULL and (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
+    end
+
+    def order_basic_bumped
+      "CASE WHEN (topics.pinned_at IS NOT NULL) THEN 0 ELSE 1 END, topics.bumped_at DESC"
+    end
+
+  end
+
   def initialize(user=nil, opts={})
     @user = user
 
@@ -64,23 +105,19 @@ class TopicQuery
 
   # The popular view of topics
   def list_popular
-    return_list(unordered: true) do |list|
-      list.order('CASE WHEN topics.category_id IS NULL and topics.pinned THEN 0 ELSE 1 END, topics.bumped_at DESC')
-    end
+    TopicList.new(@user, default_list)
   end
 
   # The favorited topics
   def list_favorited
     return_list do |list|
-      list.joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.starred AND tu.user_id = #{@user_id})")
+      list.where('tu.starred')
     end
   end
 
   def list_read
     return_list(unordered: true) do |list|
-      list
-        .joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
-        .order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
+      list.order('COALESCE(tu.last_visited_at, topics.bumped_at) DESC')
     end
   end
 
@@ -93,17 +130,30 @@ class TopicQuery
   end
 
   def list_posted
-    return_list do |list|
-      list.joins("INNER JOIN topic_users AS tu ON (tu.topic_id = topics.id AND tu.posted AND tu.user_id = #{@user_id})")
-    end
+    return_list {|l| l.where('tu.user_id IS NOT NULL') }
   end
 
   def list_uncategorized
-    return_list {|l| l.where(category_id: nil).order('topics.pinned desc')}
+    return_list(unordered: true) do |list|
+      list = list.where(category_id: nil)
+
+      if @user_id.present?
+        list.order(TopicQuery.order_with_pinned_sql)
+      else
+        list.order(TopicQuery.order_nocategory_basic_bumped)
+      end
+    end
   end
 
   def list_category(category)
-    return_list {|l| l.where(category_id: category.id).order('topics.pinned desc')}
+    return_list(unordered: true) do |list|
+      list = list.where(category_id: category.id)
+      if @user_id.present?
+        list.order(TopicQuery.order_with_pinned_sql)
+      else
+        list.order(TopicQuery.order_basic_bumped)
+      end
+    end
   end
 
   def unread_count
@@ -130,8 +180,22 @@ class TopicQuery
       query_opts = @opts.merge(list_opts)
       page_size = query_opts[:per_page] || SiteSetting.topics_per_page
 
+      # Start with a list of all topics
       result = Topic
-      result = result.topic_list_order unless query_opts[:unordered]
+
+      if @user_id.present?
+        result = result.joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
+      end
+
+      unless query_opts[:unordered]
+        # If we're logged in, we have to pay attention to our pinned settings
+        if @user_id.present?
+          result = result.order(TopicQuery.order_nocategory_with_pinned_sql)
+        else
+          result = result.order(TopicQuery.order_basic_bumped)
+        end
+      end
+
       result = result.listable_topics.includes(:category)
       result = result.where('categories.name is null or categories.name <> ?', query_opts[:exclude_category]) if query_opts[:exclude_category]
       result = result.where('categories.name = ?', query_opts[:only_category]) if query_opts[:only_category]
@@ -145,16 +209,15 @@ class TopicQuery
     def new_results(list_opts={})
 
       default_list(list_opts)
-        .joins("LEFT OUTER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id})")
         .where("topics.created_at >= :created_at", created_at: @user.treat_as_new_topic_start_date)
         .where("tu.last_read_post_number IS NULL")
-        .where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser::NotificationLevel::TRACKING)
+        .where("COALESCE(tu.notification_level, :tracking) >= :tracking", tracking: TopicUser.notification_levels[:tracking])
     end
 
     def unread_results(list_opts={})
       default_list(list_opts)
-        .joins("INNER JOIN topic_users AS tu ON (topics.id = tu.topic_id AND tu.user_id = #{@user_id} AND tu.last_read_post_number < topics.highest_post_number)")
-        .where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser::NotificationLevel::REGULAR, tracking: TopicUser::NotificationLevel::TRACKING)
+        .where("tu.last_read_post_number < topics.highest_post_number")
+        .where("COALESCE(tu.notification_level, :regular) >= :tracking", regular: TopicUser.notification_levels[:regular], tracking: TopicUser.notification_levels[:tracking])
     end
 
     def random_suggested_results_for(topic, count, exclude_topic_ids)
diff --git a/lib/unread.rb b/lib/unread.rb
index 5aed0f251..e41d37e4e 100644
--- a/lib/unread.rb
+++ b/lib/unread.rb
@@ -27,7 +27,7 @@ class Unread
   protected
 
   def do_not_notify?(notification_level)
-    [TopicUser::NotificationLevel::MUTED, TopicUser::NotificationLevel::REGULAR].include?(notification_level)
+    [TopicUser.notification_levels[:muted], TopicUser.notification_levels[:regular]].include?(notification_level)
   end
 
 end
diff --git a/spec/components/pinned_check_spec.rb b/spec/components/pinned_check_spec.rb
new file mode 100644
index 000000000..2cab45ae9
--- /dev/null
+++ b/spec/components/pinned_check_spec.rb
@@ -0,0 +1,58 @@
+require 'pinned_check'
+
+describe PinnedCheck do
+
+  #let(:topic) { Fabricate.build(:topic) }
+
+  let(:pinned_at) { 12.hours.ago }
+  let(:unpinned_topic) { Fabricate.build(:topic) }
+  let(:pinned_topic) { Fabricate.build(:topic, pinned_at: pinned_at) }
+
+  context "without a topic_user record (either anonymous or never been in the topic)" do
+
+    it "returns false if the topic is not pinned" do
+      PinnedCheck.new(unpinned_topic).should_not be_pinned
+    end
+
+    it "returns true if the topic is pinned" do
+      PinnedCheck.new(unpinned_topic).should_not be_pinned
+    end
+
+  end
+
+  context "with a topic_user record" do
+    let(:user) { Fabricate.build(:user) }
+    let(:unpinned_topic_user) { Fabricate.build(:topic_user, user: user, topic: unpinned_topic) }
+
+    describe "unpinned topic" do
+      let(:topic_user) { TopicUser.new(topic: unpinned_topic, user: user) }
+
+      it "returns false" do
+        PinnedCheck.new(unpinned_topic, topic_user).should_not be_pinned
+      end
+
+    end
+
+    describe "pinned topic" do
+      let(:topic_user) { TopicUser.new(topic: pinned_topic, user: user) }
+
+      it "is pinned if the topic_user's cleared_pinned_at is blank" do
+        PinnedCheck.new(pinned_topic, topic_user).should be_pinned
+      end
+
+      it "is not pinned if the topic_user's cleared_pinned_at is later than when it was pinned_at" do
+        topic_user.cleared_pinned_at = (pinned_at + 1.hour)
+        PinnedCheck.new(pinned_topic, topic_user).should_not be_pinned
+      end
+
+      it "is pinned if the topic_user's cleared_pinned_at is earlier than when it was pinned_at" do
+        topic_user.cleared_pinned_at = (pinned_at - 3.hours)
+        PinnedCheck.new(pinned_topic, topic_user).should be_pinned
+      end
+    end
+
+
+  end
+
+end
+
diff --git a/spec/components/topic_query_spec.rb b/spec/components/topic_query_spec.rb
index 2f5ea43e1..4536cec36 100644
--- a/spec/components/topic_query_spec.rb
+++ b/spec/components/topic_query_spec.rb
@@ -12,14 +12,13 @@ describe TopicQuery do
 
   context 'a bunch of topics' do
     let!(:regular_topic) { Fabricate(:topic, title: 'this is a regular topic', user: creator, bumped_at: 15.minutes.ago) }
-    let!(:pinned_topic) { Fabricate(:topic, title: 'this is a pinned topic', user: creator, pinned: true, bumped_at: 10.minutes.ago) }
+    let!(:pinned_topic) { Fabricate(:topic, title: 'this is a pinned topic', user: creator, pinned_at: 10.minutes.ago, bumped_at: 10.minutes.ago) }
     let!(:archived_topic) { Fabricate(:topic, title: 'this is an archived topic', user: creator, archived: true, bumped_at: 6.minutes.ago) }
     let!(:invisible_topic) { Fabricate(:topic, title: 'this is an invisible topic', user: creator, visible: false, bumped_at: 5.minutes.ago) }
     let!(:closed_topic) { Fabricate(:topic, title: 'this is a closed topic', user: creator, closed: true, bumped_at: 1.minute.ago) }
+    let(:topics) { topic_query.list_popular.topics }
 
     context 'list_popular' do
-      let(:topics) { topic_query.list_popular.topics }
-
       it "returns the topics in the correct order" do
         topics.should == [pinned_topic, closed_topic, archived_topic, regular_topic]
       end
@@ -33,6 +32,17 @@ describe TopicQuery do
       end
     end
 
+    context 'after clearring a pinned topic' do
+      before do
+        pinned_topic.clear_pin_for(user)
+      end
+
+      it "no longer shows the pinned topic at the top" do
+        topics.should == [closed_topic, archived_topic, pinned_topic, regular_topic]
+      end
+
+    end
+
   end
 
   context 'categorized' do
diff --git a/spec/components/unread_spec.rb b/spec/components/unread_spec.rb
index c567ee9bd..e40b9d2a2 100644
--- a/spec/components/unread_spec.rb
+++ b/spec/components/unread_spec.rb
@@ -7,8 +7,8 @@ describe Unread do
   before do
     @topic = Fabricate(:topic, posts_count: 13, highest_post_number: 13)
     @topic_user = TopicUser.get(@topic, @topic.user)
-    @topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::TRACKING)
-    @topic_user.notification_level = TopicUser::NotificationLevel::TRACKING
+    @topic_user.stubs(:notification_level).returns(TopicUser.notification_levels[:tracking])
+    @topic_user.notification_level = TopicUser.notification_levels[:tracking]
     @unread = Unread.new(@topic, @topic_user)
   end
 
@@ -51,7 +51,7 @@ describe Unread do
 
     it 'has 0 new posts if the user has read 10 posts but is not tracking' do
       @topic_user.stubs(:seen_post_count).returns(10)
-      @topic_user.stubs(:notification_level).returns(TopicUser::NotificationLevel::REGULAR)
+      @topic_user.stubs(:notification_level).returns(TopicUser.notification_levels[:regular])
       @unread.new_posts.should == 0
     end
 
diff --git a/spec/controllers/topics_controller_spec.rb b/spec/controllers/topics_controller_spec.rb
index d7a463ff2..5c78aa7ac 100644
--- a/spec/controllers/topics_controller_spec.rb
+++ b/spec/controllers/topics_controller_spec.rb
@@ -74,6 +74,37 @@ describe TopicsController do
 
   end
 
+  context 'clear_pin' do
+    it 'needs you to be logged in' do
+      lambda { xhr :put, :clear_pin, topic_id: 1 }.should raise_error(Discourse::NotLoggedIn)
+    end
+
+    context 'when logged in' do
+      let(:topic) { Fabricate(:topic) }
+      let!(:user) { log_in }
+
+      it "fails when the user can't see the topic" do
+        Guardian.any_instance.expects(:can_see?).with(topic).returns(false)
+        xhr :put, :clear_pin, topic_id: topic.id
+        response.should_not be_success
+      end
+
+      describe 'when the user can see the topic' do
+        it "calls clear_pin_for if the user can see the topic" do
+          Topic.any_instance.expects(:clear_pin_for).with(user).once
+          xhr :put, :clear_pin, topic_id: topic.id
+        end
+
+        it "succeeds" do
+          xhr :put, :clear_pin, topic_id: topic.id
+          response.should be_success
+        end
+      end
+
+    end
+
+  end
+
   context 'status' do
     it 'needs you to be logged in' do
       lambda { xhr :put, :status, topic_id: 1, status: 'visible', enabled: true }.should raise_error(Discourse::NotLoggedIn)
diff --git a/spec/models/topic_spec.rb b/spec/models/topic_spec.rb
index 1991ff712..f8f669e08 100644
--- a/spec/models/topic_spec.rb
+++ b/spec/models/topic_spec.rb
@@ -547,8 +547,12 @@ describe Topic do
           @topic.reload
         end
 
+        it "doesn't have a pinned_at" do
+          @topic.pinned_at.should be_blank
+        end
+
         it 'should not be pinned' do
-          @topic.should_not be_pinned
+          @topic.pinned_at.should be_blank
         end
 
         it 'adds a moderator post' do
@@ -562,13 +566,13 @@ describe Topic do
 
       context 'enable' do
         before do
-          @topic.update_attribute :pinned, false
+          @topic.update_attribute :pinned_at, nil
           @topic.update_status('pinned', true, @user)
           @topic.reload
         end
 
         it 'should be pinned' do
-          @topic.should be_pinned
+          @topic.pinned_at.should be_present
         end
 
         it 'adds a moderator post' do
@@ -588,7 +592,7 @@ describe Topic do
           @topic.reload
         end
 
-        it 'should not be pinned' do
+        it 'should not be archived' do
           @topic.should_not be_archived
         end
 
@@ -866,8 +870,12 @@ describe Topic do
       topic.should be_visible
     end
 
+    it "has an empty pinned_at" do
+      topic.pinned_at.should be_blank
+    end
+
     it 'is not pinned' do
-      topic.should_not be_pinned
+      topic.pinned_at.should be_blank
     end
 
     it 'is not closed' do
diff --git a/spec/models/topic_user_spec.rb b/spec/models/topic_user_spec.rb
index 41daf770e..60a7e61a3 100644
--- a/spec/models/topic_user_spec.rb
+++ b/spec/models/topic_user_spec.rb
@@ -5,155 +5,165 @@ describe TopicUser do
   it { should belong_to :user }
   it { should belong_to :topic }
 
+  let!(:yesterday) { DateTime.now.yesterday }
+
   before do
-    #mock time so we can test dates
-    @now = DateTime.now.yesterday
-    DateTime.expects(:now).at_least_once.returns(@now)
-    @topic = Fabricate(:topic)
-    @user = Fabricate(:coding_horror)
+    DateTime.expects(:now).at_least_once.returns(yesterday)
+  end
+
+  let!(:topic) { Fabricate(:topic) }
+  let!(:user) { Fabricate(:coding_horror) }
+  let(:topic_user) { TopicUser.get(topic,user) }
+  let(:topic_creator_user) { TopicUser.get(topic, topic.user) }
+
+  let(:post) { Fabricate(:post, topic: topic, user: user) }
+  let(:new_user) { Fabricate(:user, auto_track_topics_after_msecs: 1000) }
+  let(:topic_new_user) { TopicUser.get(topic, new_user)}
+
+
+  describe "unpinned" do
+
+    before do
+      TopicUser.change(user, topic, {:starred_at => yesterday})
+    end
+
+    it "defaults to blank" do
+      topic_user.cleared_pinned_at.should be_blank
+    end
+
   end
 
   describe 'notifications' do
 
     it 'should be set to tracking if auto_track_topics is enabled' do
-      @user.auto_track_topics_after_msecs = 0
-      @user.save
-      TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
-      TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
+      user.update_column(:auto_track_topics_after_msecs, 0)
+      TopicUser.change(user, topic, {:starred_at => yesterday})
+      TopicUser.get(topic, user).notification_level.should == TopicUser.notification_levels[:tracking]
     end
 
     it 'should reset regular topics to tracking topics if auto track is changed' do
-      TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
-      @user.auto_track_topics_after_msecs = 0
-      @user.save
-      TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::TRACKING
+      TopicUser.change(user, topic, {:starred_at => yesterday})
+      user.auto_track_topics_after_msecs = 0
+      user.save
+      topic_user.notification_level.should == TopicUser.notification_levels[:tracking]
     end
 
     it 'should be set to "regular" notifications, by default on non creators' do
-      TopicUser.change(@user, @topic, {:starred_at => DateTime.now})
-      TopicUser.get(@topic,@user).notification_level.should == TopicUser::NotificationLevel::REGULAR
+      TopicUser.change(user, topic, {:starred_at => yesterday})
+      TopicUser.get(topic,user).notification_level.should == TopicUser.notification_levels[:regular]
     end
 
     it 'reason should reset when changed' do
-      @topic.notify_muted!(@topic.user)
-      TopicUser.get(@topic,@topic.user).notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
+      topic.notify_muted!(topic.user)
+      TopicUser.get(topic,topic.user).notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
     end
 
     it 'should have the correct reason for a user change when watched' do
-      @topic.notify_watch!(@user)
-      tu = TopicUser.get(@topic,@user)
-      tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
-      tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
-      tu.notifications_changed_at.should_not be_nil
+      topic.notify_watch!(user)
+      topic_user.notification_level.should == TopicUser.notification_levels[:watching]
+      topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
+      topic_user.notifications_changed_at.should_not be_nil
     end
 
     it 'should have the correct reason for a user change when set to regular' do
-      @topic.notify_regular!(@user)
-      tu = TopicUser.get(@topic,@user)
-      tu.notification_level.should == TopicUser::NotificationLevel::REGULAR
-      tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
-      tu.notifications_changed_at.should_not be_nil
+      topic.notify_regular!(user)
+      topic_user.notification_level.should == TopicUser.notification_levels[:regular]
+      topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
+      topic_user.notifications_changed_at.should_not be_nil
     end
 
     it 'should have the correct reason for a user change when set to regular' do
-      @topic.notify_muted!(@user)
-      tu = TopicUser.get(@topic,@user)
-      tu.notification_level.should == TopicUser::NotificationLevel::MUTED
-      tu.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
-      tu.notifications_changed_at.should_not be_nil
+      topic.notify_muted!(user)
+      topic_user.notification_level.should == TopicUser.notification_levels[:muted]
+      topic_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
+      topic_user.notifications_changed_at.should_not be_nil
     end
 
     it 'should watch topics a user created' do
-      tu = TopicUser.get(@topic,@topic.user)
-      tu.notification_level.should == TopicUser::NotificationLevel::WATCHING
-      tu.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_TOPIC
+      topic_creator_user.notification_level.should == TopicUser.notification_levels[:watching]
+      topic_creator_user.notifications_reason_id.should == TopicUser.notification_reasons[:created_topic]
     end
   end
 
   describe 'visited at' do
-    before do
-      TopicUser.track_visit!(@topic, @user)
-      @topic_user = TopicUser.get(@topic,@user)
 
+    before do
+      TopicUser.track_visit!(topic, user)
     end
 
     it 'set upon initial visit' do
-      @topic_user.first_visited_at.to_i.should == @now.to_i
-      @topic_user.last_visited_at.to_i.should == @now.to_i
+      topic_user.first_visited_at.to_i.should == yesterday.to_i
+      topic_user.last_visited_at.to_i.should == yesterday.to_i
     end
 
     it 'updates upon repeat visit' do
-      tomorrow = @now.tomorrow
-      DateTime.expects(:now).returns(tomorrow)
+      today = yesterday.tomorrow
+      DateTime.expects(:now).returns(today)
 
-      TopicUser.track_visit!(@topic,@user)
+      TopicUser.track_visit!(topic,user)
       # reload is a no go
-      @topic_user = TopicUser.get(@topic,@user)
-      @topic_user.first_visited_at.to_i.should == @now.to_i
-      @topic_user.last_visited_at.to_i.should == tomorrow.to_i
+      topic_user = TopicUser.get(topic,user)
+      topic_user.first_visited_at.to_i.should == yesterday.to_i
+      topic_user.last_visited_at.to_i.should == today.to_i
     end
 
   end
 
   describe 'read tracking' do
-    before do
-      @post = Fabricate(:post, topic: @topic, user: @topic.user)
-      TopicUser.update_last_read(@user, @topic.id, 1, 0)
-      @topic_user = TopicUser.get(@topic,@user)
-    end
 
-    it 'should create a new record for a visit' do
-      @topic_user.last_read_post_number.should == 1
-      @topic_user.last_visited_at.to_i.should == @now.to_i
-      @topic_user.first_visited_at.to_i.should == @now.to_i
-    end
+    context "without auto tracking" do
 
-    it 'should update the record for repeat visit' do
-      Fabricate(:post, topic: @topic, user: @user)
-      TopicUser.update_last_read(@user, @topic.id, 2, 0)
-      @topic_user = TopicUser.get(@topic,@user)
-      @topic_user.last_read_post_number.should == 2
-      @topic_user.last_visited_at.to_i.should == @now.to_i
-      @topic_user.first_visited_at.to_i.should == @now.to_i
+      before do
+        TopicUser.update_last_read(user, topic.id, 1, 0)
+      end
+
+      let(:topic_user) { TopicUser.get(topic,user) }
+
+      it 'should create a new record for a visit' do
+        topic_user.last_read_post_number.should == 1
+        topic_user.last_visited_at.to_i.should == yesterday.to_i
+        topic_user.first_visited_at.to_i.should == yesterday.to_i
+      end
+
+      it 'should update the record for repeat visit' do
+        Fabricate(:post, topic: topic, user: user)
+        TopicUser.update_last_read(user, topic.id, 2, 0)
+        topic_user = TopicUser.get(topic,user)
+        topic_user.last_read_post_number.should == 2
+        topic_user.last_visited_at.to_i.should == yesterday.to_i
+        topic_user.first_visited_at.to_i.should == yesterday.to_i
+      end
     end
 
     context 'auto tracking' do
+
       before do
-        Fabricate(:post, topic: @topic, user: @user)
-        @new_user = Fabricate(:user, auto_track_topics_after_msecs: 1000)
-        TopicUser.update_last_read(@new_user, @topic.id, 2, 0)
-        @topic_user = TopicUser.get(@topic,@new_user)
+        TopicUser.update_last_read(new_user, topic.id, 2, 0)
       end
 
       it 'should automatically track topics you reply to' do
-        post = Fabricate(:post, topic: @topic, user: @new_user)
-        @topic_user = TopicUser.get(@topic,@new_user)
-        @topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
-        @topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::CREATED_POST
+        post = Fabricate(:post, topic: topic, user: new_user)
+        topic_new_user.notification_level.should == TopicUser.notification_levels[:tracking]
+        topic_new_user.notifications_reason_id.should == TopicUser.notification_reasons[:created_post]
       end
 
       it 'should not automatically track topics you reply to and have set state manually' do
-        Fabricate(:post, topic: @topic, user: @new_user)
-        TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
-        @topic_user = TopicUser.get(@topic,@new_user)
-        @topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
-        @topic_user.notifications_reason_id.should == TopicUser::NotificationReasons::USER_CHANGED
+        Fabricate(:post, topic: topic, user: new_user)
+        TopicUser.change(new_user, topic, notification_level: TopicUser.notification_levels[:regular])
+        topic_new_user.notification_level.should == TopicUser.notification_levels[:regular]
+        topic_new_user.notifications_reason_id.should == TopicUser.notification_reasons[:user_changed]
       end
 
       it 'should automatically track topics after they are read for long enough' do
-        @topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
-        TopicUser.update_last_read(@new_user, @topic.id, 2, 1001)
-        @topic_user = TopicUser.get(@topic,@new_user)
-        @topic_user.notification_level.should == TopicUser::NotificationLevel::TRACKING
+        topic_new_user.notification_level.should ==TopicUser.notification_levels[:regular]
+        TopicUser.update_last_read(new_user, topic.id, 2, 1001)
+        TopicUser.get(topic, new_user).notification_level.should == TopicUser.notification_levels[:tracking]
       end
 
       it 'should not automatically track topics after they are read for long enough if changed manually' do
-        TopicUser.change(@new_user, @topic, notification_level: TopicUser::NotificationLevel::REGULAR)
-        @topic_user = TopicUser.get(@topic,@new_user)
-
-        TopicUser.update_last_read(@new_user, @topic, 2, 1001)
-        @topic_user = TopicUser.get(@topic,@new_user)
-        @topic_user.notification_level.should == TopicUser::NotificationLevel::REGULAR
+        TopicUser.change(new_user, topic, notification_level: TopicUser.notification_levels[:regular])
+        TopicUser.update_last_read(new_user, topic, 2, 1001)
+        topic_new_user.notification_level.should == TopicUser.notification_levels[:regular]
       end
     end
   end
@@ -162,34 +172,33 @@ describe TopicUser do
 
     it 'creates a forum topic user record' do
       lambda {
-        TopicUser.change(@user, @topic.id, starred: true)
+        TopicUser.change(user, topic.id, starred: true)
       }.should change(TopicUser, :count).by(1)
     end
 
     it "only inserts a row once, even on repeated calls" do
       lambda {
-        TopicUser.change(@user, @topic.id, starred: true)
-        TopicUser.change(@user, @topic.id, starred: false)
-        TopicUser.change(@user, @topic.id, starred: true)
+        TopicUser.change(user, topic.id, starred: true)
+        TopicUser.change(user, topic.id, starred: false)
+        TopicUser.change(user, topic.id, starred: true)
       }.should change(TopicUser, :count).by(1)
     end
 
     describe 'after creating a row' do
       before do
-        TopicUser.change(@user, @topic.id, starred: true)
-        @topic_user = TopicUser.where(user_id: @user.id, topic_id: @topic.id).first
+        TopicUser.change(user, topic.id, starred: true)
       end
 
       it 'has the correct starred value' do
-        @topic_user.should be_starred
+        TopicUser.get(topic, user).should be_starred
       end
 
       it 'has a lookup' do
-        TopicUser.lookup_for(@user, [@topic]).should be_present
+        TopicUser.lookup_for(user, [topic]).should be_present
       end
 
       it 'has a key in the lookup for this forum topic' do
-        TopicUser.lookup_for(@user, [@topic]).has_key?(@topic.id).should be_true
+        TopicUser.lookup_for(user, [topic]).has_key?(topic.id).should be_true
       end
 
     end