diff --git a/Gemfile b/Gemfile
index 59eb08814f1c62b3a210e41a0f9dcda413b803f2..f0a77f7b6403c53649102f333d478888e8043f6e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -38,6 +38,8 @@ group :development do
   gem 'web-console', '~> 2.0'
   gem 'spring'
   gem 'rubocop', require: false
+  gem 'better_errors'
+  gem 'binding_of_caller'
 end
 
 group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
index ff72f4ddf159a903bf95def300620edf39643a2b..fad87d1914c5ae3182d1d9b84da81a9608dcbe63 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -43,6 +43,10 @@ GEM
       descendants_tracker (~> 0.0.4)
       ice_nine (~> 0.11.0)
       thread_safe (~> 0.3, >= 0.3.1)
+    better_errors (2.1.1)
+      coderay (>= 1.0.0)
+      erubis (>= 2.6.6)
+      rack (>= 0.9.0)
     binding_of_caller (0.7.2)
       debug_inspector (>= 0.0.1)
     builder (3.2.2)
@@ -284,6 +288,8 @@ PLATFORMS
 
 DEPENDENCIES
   addressable
+  better_errors
+  binding_of_caller
   byebug
   coffee-rails (~> 4.1.0)
   dotenv-rails
diff --git a/app/api/mastodon/entities.rb b/app/api/mastodon/entities.rb
index a3f40ec48afc03582c2cda02c98ce21e366a658e..2e56a67df4303bbb0dc5d32d0d201ee6d1113300 100644
--- a/app/api/mastodon/entities.rb
+++ b/app/api/mastodon/entities.rb
@@ -3,6 +3,8 @@ module Mastodon
     class Account < Grape::Entity
       expose :username
       expose :domain
+      expose :display_name
+      expose :note
     end
 
     class Status < Grape::Entity
diff --git a/app/api/mastodon/ostatus.rb b/app/api/mastodon/ostatus.rb
index fcde980f712e6b1f0dd8a28b7f0069b20a318e52..4676bc429ee77ec97e587514d5b7bcc2c47b600c 100644
--- a/app/api/mastodon/ostatus.rb
+++ b/app/api/mastodon/ostatus.rb
@@ -8,12 +8,10 @@ module Mastodon
 
     resource :subscriptions do
       helpers do
-        def subscription_url(account)
-          "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}"
-        end
+        include ApplicationHelper
       end
 
-      desc 'Receive updates from a feed'
+      desc 'Receive updates from an account'
 
       params do
         requires :id, type: String, desc: 'Account ID'
@@ -23,14 +21,14 @@ module Mastodon
         body = request.body.read
 
         if @account.subscription(subscription_url(@account)).verify(body, env['HTTP_X_HUB_SIGNATURE'])
-          ProcessFeedUpdateService.new.(body, @account)
+          ProcessFeedService.new.(body, @account)
           status 201
         else
           status 202
         end
       end
 
-      desc 'Confirm PuSH subscription to a feed'
+      desc 'Confirm PuSH subscription to an account'
 
       params do
         requires :id, type: String, desc: 'Account ID'
@@ -49,14 +47,15 @@ module Mastodon
     end
 
     resource :salmon do
-      desc 'Receive Salmon updates'
+      desc 'Receive Salmon updates targeted to account'
 
       params do
         requires :id, type: String, desc: 'Account ID'
       end
 
       post ':id' do
-        # todo
+        ProcessInteractionService.new.(request.body.read, @account)
+        status 201
       end
     end
   end
diff --git a/app/api/mastodon/rest.rb b/app/api/mastodon/rest.rb
index e011ab34dea3284b4504df64770001f17d0c5b19..eaf337938e60c5b23905aa9fc110335a70ceb983 100644
--- a/app/api/mastodon/rest.rb
+++ b/app/api/mastodon/rest.rb
@@ -5,9 +5,34 @@ module Mastodon
 
     resource :statuses do
       desc 'Return a public timeline'
+
       get :all do
         present Status.all, with: Mastodon::Entities::Status
       end
+
+      desc 'Return the home timeline of a logged in user'
+
+      get :home do
+        # todo
+      end
+
+      desc 'Return the notifications timeline of a logged in user'
+
+      get :notifications do
+        # todo
+      end
+    end
+
+    resource :accounts do
+      desc 'Return a user profile'
+
+      params do
+        requires :id, type: String, desc: 'Account ID'
+      end
+
+      get ':id' do
+        present Account.find(params[:id]), with: Mastodon::Entities::Account
+      end
     end
   end
 end
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
index e07c5a830f77cc94a7a7e89025319b449838d899..646c5aba4eb6c8533023dc183a98537bf063b0cc 100644
--- a/app/assets/javascripts/application.js
+++ b/app/assets/javascripts/application.js
@@ -12,5 +12,4 @@
 //
 //= require jquery
 //= require jquery_ujs
-//= require turbolinks
 //= require_tree .
diff --git a/app/assets/javascripts/atom.coffee b/app/assets/javascripts/atom.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..24f83d18bbd38c24c4f7c3c2fc360cd68e857a2a
--- /dev/null
+++ b/app/assets/javascripts/atom.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/home.coffee b/app/assets/javascripts/home.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..24f83d18bbd38c24c4f7c3c2fc360cd68e857a2a
--- /dev/null
+++ b/app/assets/javascripts/home.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/profile.coffee b/app/assets/javascripts/profile.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..24f83d18bbd38c24c4f7c3c2fc360cd68e857a2a
--- /dev/null
+++ b/app/assets/javascripts/profile.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/xrd.coffee b/app/assets/javascripts/xrd.coffee
new file mode 100644
index 0000000000000000000000000000000000000000..24f83d18bbd38c24c4f7c3c2fc360cd68e857a2a
--- /dev/null
+++ b/app/assets/javascripts/xrd.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/atom.scss b/app/assets/stylesheets/atom.scss
new file mode 100644
index 0000000000000000000000000000000000000000..888698db31039c04668b0e71b7cc3256dc0ff668
--- /dev/null
+++ b/app/assets/stylesheets/atom.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Atom controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss
new file mode 100644
index 0000000000000000000000000000000000000000..7131aac4df46d9ecf7a1209ac3cacd8e6659485d
--- /dev/null
+++ b/app/assets/stylesheets/home.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Home controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/profile.scss b/app/assets/stylesheets/profile.scss
new file mode 100644
index 0000000000000000000000000000000000000000..22ee50876879d817694bea9e39796e5c2bf5ef32
--- /dev/null
+++ b/app/assets/stylesheets/profile.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Profile controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/xrd.scss b/app/assets/stylesheets/xrd.scss
new file mode 100644
index 0000000000000000000000000000000000000000..62391c7d37cd0a3e9e404208acad0ebe383a4b6e
--- /dev/null
+++ b/app/assets/stylesheets/xrd.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the XRD controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/atom_controller.rb b/app/controllers/atom_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e0b45c5800a8385aa58f7b327e2f020477a0e37d
--- /dev/null
+++ b/app/controllers/atom_controller.rb
@@ -0,0 +1,14 @@
+class AtomController < ApplicationController
+  before_filter :set_format
+
+  def user_stream
+    @account = Account.find_by!(id: params[:id], domain: nil)
+  end
+
+  private
+
+  def set_format
+    request.format = 'xml'
+    response.headers['Content-Type'] = 'application/atom+xml'
+  end
+end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..95f29929cad2de8b30a4d823197d72354bfb9114
--- /dev/null
+++ b/app/controllers/home_controller.rb
@@ -0,0 +1,4 @@
+class HomeController < ApplicationController
+  def index
+  end
+end
diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2374318ebbd27cec44397d60fba70461c8f052cb
--- /dev/null
+++ b/app/controllers/profile_controller.rb
@@ -0,0 +1,4 @@
+class ProfileController < ApplicationController
+  def show
+  end
+end
diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4c8e958e650a30e045b8dc6be4189d07969898ae
--- /dev/null
+++ b/app/controllers/xrd_controller.rb
@@ -0,0 +1,39 @@
+class XrdController < ApplicationController
+  before_filter :set_format
+
+  def host_meta
+    @webfinger_template = "#{webfinger_url}?resource={uri}"
+  end
+
+  def webfinger
+    @account = Account.find_by!(username: username_from_resource, domain: nil)
+    @canonical_account_uri = "acct:#{@account.username}#{LOCAL_DOMAIN}"
+    @magic_key = pem_to_magic_key(@account.keypair.public_key)
+  end
+
+  private
+
+  def set_format
+    request.format = 'xml'
+    response.headers['Content-Type'] = 'application/xrd+xml'
+  end
+
+  def username_from_resource
+    params[:resource].split('@').first.gsub('acct:', '')
+  end
+
+  def pem_to_magic_key(public_key)
+    modulus, exponent = [public_key.n, public_key.e].map do |component|
+      result = ""
+
+      until component == 0 do
+        result << [component % 256].pack('C')
+        component >>= 8
+      end
+
+      result.reverse!
+    end
+
+    (["RSA"] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
+  end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index de6be7945c6a59798eb0ace177df38b05e98c2f0..29e444a32735a0ae06a77961a15faca6e975ca64 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,2 +1,19 @@
 module ApplicationHelper
+  include GrapeRouteHelpers::NamedRouteMatcher
+
+  def unique_tag(date, id, type)
+    "tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
+  end
+
+  def subscription_url(account)
+    add_base_url_prefix subscription_path(id: account.id, format: '')
+  end
+
+  def salmon_url(account)
+    add_base_url_prefix salmon_path(id: account.id, format: '')
+  end
+
+  def add_base_url_prefix(suffix)
+    "#{root_url}api#{suffix}"
+  end
 end
diff --git a/app/helpers/atom_helper.rb b/app/helpers/atom_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..a42a4994688038792ef9a71d9a5e99dea37b8307
--- /dev/null
+++ b/app/helpers/atom_helper.rb
@@ -0,0 +1,5 @@
+module AtomHelper
+  def stream_updated_at
+    @account.stream_entries.last ? @account.stream_entries.last.created_at.iso8601 : @account.updated_at.iso8601
+  end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..23de56ac60aace0b1cc98dba68407ddcecc7614a
--- /dev/null
+++ b/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5a0d6b31f8c68367f5262133e2edf36c4a2ebc8f
--- /dev/null
+++ b/app/helpers/profile_helper.rb
@@ -0,0 +1,2 @@
+module ProfileHelper
+end
diff --git a/app/helpers/xrd_helper.rb b/app/helpers/xrd_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..6b273e1226aaac9aaee6f0f1665dbc715913c515
--- /dev/null
+++ b/app/helpers/xrd_helper.rb
@@ -0,0 +1,2 @@
+module XrdHelper
+end
diff --git a/app/models/account.rb b/app/models/account.rb
index c0b153794dd90de9aaa95ec8759cf6e8b15684ea..90e8d7610b8f8fdd036c27b534563bd1586bb2bf 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -1,6 +1,38 @@
 class Account < ActiveRecord::Base
+  # Local users
+  has_one :user, inverse_of: :account
+
+  # Timelines
+  has_many :stream_entries, inverse_of: :account
   has_many :statuses, inverse_of: :account
 
+  # Follow relations
+  has_many :active_relationships,  class_name: 'Follow', foreign_key: 'account_id',        dependent: :destroy
+  has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
+
+  has_many :following, through: :active_relationships,  source: :target_account
+  has_many :followers, through: :passive_relationships, source: :account
+
+  def follow!(other_account)
+    self.active_relationships.create!(target_account: other_account)
+  end
+
+  def unfollow!(other_account)
+    self.active_relationships.find_by(target_account: other_account).destroy
+  end
+
+  def following?(other_account)
+    following.include?(other_account)
+  end
+
+  def local?
+    self.domain.nil?
+  end
+
+  def keypair
+    self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key)
+  end
+
   def subscription(webhook_url)
     @subscription ||= OStatus2::Subscription.new(self.remote_url, secret: self.secret, token: self.verify_token, webhook: webhook_url, hub: self.hub_url)
   end
diff --git a/app/models/follow.rb b/app/models/follow.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eec01b9ba2a61512d146028d8f3f11afbda6d1a4
--- /dev/null
+++ b/app/models/follow.rb
@@ -0,0 +1,8 @@
+class Follow < ActiveRecord::Base
+  belongs_to :account
+  belongs_to :target_account, class_name: 'Account'
+
+  after_create do
+    self.account.stream_entries.create!(activity: self)
+  end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
index a1278ccaa217912c6446a9b86568cf4cdfceab1e..d98297643f0ddef650eb57f36565c5128949b34d 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -1,3 +1,7 @@
 class Status < ActiveRecord::Base
   belongs_to :account, inverse_of: :statuses
+
+  after_create do
+    self.account.stream_entries.create!(activity: self)
+  end
 end
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
new file mode 100644
index 0000000000000000000000000000000000000000..cee151a077277b204e9e4c0ca60a30123eed8f20
--- /dev/null
+++ b/app/models/stream_entry.rb
@@ -0,0 +1,33 @@
+class StreamEntry < ActiveRecord::Base
+  belongs_to :account, inverse_of: :stream_entries
+  belongs_to :activity, polymorphic: true
+
+  def object_type
+    case self.activity_type
+    when 'Status'
+      :note
+    when 'Follow'
+      :person
+    end
+  end
+
+  def verb
+    case self.activity_type
+    when 'Status'
+      :post
+    when 'Follow'
+      :follow
+    end
+  end
+
+  def target
+    case self.activity_type
+    when 'Follow'
+      self.activity.target_account
+    end
+  end
+
+  def content
+    self.activity.text if self.activity_type == 'Status'
+  end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ccfa54e4f49d352c5f1354e99bab6e40baf3c8e8
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,3 @@
+class User < ActiveRecord::Base
+  belongs_to :account, inverse_of: :user
+end
diff --git a/app/services/fetch_feed_service.rb b/app/services/fetch_feed_service.rb
index 3b8efbe3b62244c348f68d45b4c2ae7af88daad8..059d65925547ffef17ba5f15aeff9ddcaeaf6813 100644
--- a/app/services/fetch_feed_service.rb
+++ b/app/services/fetch_feed_service.rb
@@ -1,5 +1,15 @@
 class FetchFeedService
   def call(account)
-    # todo
+    process_service.(http_client.get(account.remote_url), account)
+  end
+
+  private
+
+  def process_service
+    ProcessFeedService.new
+  end
+
+  def http_client
+    HTTP
   end
 end
diff --git a/app/services/follow_remote_user_service.rb b/app/services/follow_remote_account_service.rb
similarity index 81%
rename from app/services/follow_remote_user_service.rb
rename to app/services/follow_remote_account_service.rb
index f3c0e68df51cbadea5758df38084edad2d492b6c..41f8fa4a0a81e61207a25e65ebdc82ee67360664 100644
--- a/app/services/follow_remote_user_service.rb
+++ b/app/services/follow_remote_account_service.rb
@@ -1,14 +1,14 @@
-class FollowRemoteUserService
-  include GrapeRouteHelpers::NamedRouteMatcher
+class FollowRemoteAccountService
+  include ApplicationHelper
 
-  def call(user)
-    username, domain = user.split('@')
+  def call(uri)
+    username, domain = uri.split('@')
     account = Account.where(username: username, domain: domain).first
 
     return account unless account.nil?
 
     account = Account.new(username: username, domain: domain)
-    data    = Goldfinger.finger("acct:#{user}")
+    data    = Goldfinger.finger("acct:#{uri}")
 
     account.remote_url  = data.link('http://schemas.google.com/g/2010#updates-from').href
     account.salmon_url  = data.link('salmon').href
@@ -21,8 +21,9 @@ class FollowRemoteUserService
     feed = get_feed(account.remote_url)
     hubs = feed.xpath('//xmlns:link[@rel="hub"]')
 
-    return false if hubs.empty? || hubs.first.attribute('href').nil?
+    return false if hubs.empty? || hubs.first.attribute('href').nil? || feed.at_xpath('/xmlns:author/xmlns:uri').nil?
 
+    account.uri     = feed.at_xpath('/xmlns:author/xmlns:uri').content
     account.hub_url = hubs.first.attribute('href').value
     account.save!
 
@@ -45,7 +46,7 @@ class FollowRemoteUserService
 
     key   = OpenSSL::PKey::RSA.new
     key.n = modulus
-    key.d = exponent
+    key.e = exponent
 
     key.to_pem
   end
@@ -53,8 +54,4 @@ class FollowRemoteUserService
   def http_client
     HTTP
   end
-
-  def subscription_url(account)
-    "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}"
-  end
 end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..fc606730b837058dcc667a2737560f30d81c4585
--- /dev/null
+++ b/app/services/follow_service.rb
@@ -0,0 +1,12 @@
+class FollowService
+  def call(source_account, uri)
+    target_account = follow_remote_account_service.(uri)
+    source_account.follow!(target_account)
+  end
+
+  private
+
+  def follow_remote_account_service
+    FollowRemoteAccountService.new
+  end
+end
diff --git a/app/services/process_feed_update_service.rb b/app/services/process_feed_service.rb
similarity index 94%
rename from app/services/process_feed_update_service.rb
rename to app/services/process_feed_service.rb
index 0585fad7a58289c2121b90cde84ccac1a5f23ed5..f2523a3133685a722587a541d039d333f257a195 100644
--- a/app/services/process_feed_update_service.rb
+++ b/app/services/process_feed_service.rb
@@ -1,4 +1,4 @@
-class ProcessFeedUpdateService
+class ProcessFeedService
   def call(body, account)
     xml = Nokogiri::XML(body)
 
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8262ead8f59e8877c0997b7cf47e0993d8f091b0
--- /dev/null
+++ b/app/services/process_interaction_service.rb
@@ -0,0 +1,38 @@
+class ProcessInteractionService
+  def call(envelope, target_account)
+    body = salmon.unpack(envelope)
+    xml  = Nokogiri::XML(body)
+
+    return if xml.at_xpath('//author/name').nil? || xml.at_xpath('//author/uri').nil?
+
+    username = xml.at_xpath('//author/name').content
+    url      = xml.at_xpath('//author/uri').content
+    domain   = Addressable::URI.parse(url).host
+    account  = Account.find_by(username: username, domain: domain)
+
+    if account.nil?
+      account = follow_remote_account_service.("acct:#{username}@#{domain}")
+    end
+
+    if salmon.verify(envelope, account.keypair)
+      verb = xml.at_path('//activity:verb').content
+
+      case verb
+      when 'http://activitystrea.ms/schema/1.0/follow', 'follow'
+        account.follow!(target_account)
+      when 'http://activitystrea.ms/schema/1.0/unfollow', 'unfollow'
+        account.unfollow!(target_account)
+      end
+    end
+  end
+
+  private
+
+  def salmon
+    OStatus2::Salmon.new
+  end
+
+  def follow_remote_account_service
+    FollowRemoteAccountService.new
+  end
+end
diff --git a/app/services/setup_local_account_service.rb b/app/services/setup_local_account_service.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c40e51855c981c716bc0813981c7fa280e2aa414
--- /dev/null
+++ b/app/services/setup_local_account_service.rb
@@ -0,0 +1,14 @@
+class SetupLocalAccountService
+  def call(user, username)
+    user.build_account
+
+    user.account.username = username
+    user.account.domain   = nil
+
+    keypair = OpenSSL::PKey::RSA.new(2048)
+    user.account.private_key = keypair.to_pem
+    user.account.public_key  = keypair.public_key.to_pem
+
+    user.save!
+  end
+end
diff --git a/app/views/atom/user_stream.xml.ruby b/app/views/atom/user_stream.xml.ruby
new file mode 100644
index 0000000000000000000000000000000000000000..d418ea0ecbfcac46b44a6aef20928175d5a55612
--- /dev/null
+++ b/app/views/atom/user_stream.xml.ruby
@@ -0,0 +1,35 @@
+Nokogiri::XML::Builder.new do |xml|
+  xml.feed(xmlns: 'http://www.w3.org/2005/Atom', 'xmlns:thr': 'http://purl.org/syndication/thread/1.0', 'xmlns:activity': 'http://activitystrea.ms/spec/1.0/') do
+    xml.id_ atom_user_stream_url(id: @account.id)
+    xml.title @account.display_name
+    xml.subtitle @account.note
+    xml.updated stream_updated_at
+
+    xml.author do
+      xml['activity'].send('object-type', 'http://activitystrea.ms/schema/1.0/person')
+      xml.uri profile_url(name: @account.username)
+      xml.name @account.username
+      xml.summary @account.note
+
+      xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
+    end
+
+    xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
+    xml.link(rel: 'hub', href: '')
+    xml.link(rel: 'salmon', href: salmon_url(@account))
+    xml.link(rel: 'self', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
+
+    @account.stream_entries.each do |stream_entry|
+      xml.entry do
+        xml.id_ unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type)
+        xml.published stream_entry.activity.created_at.iso8601
+        xml.updated   stream_entry.activity.updated_at.iso8601
+        xml.content({ type: 'html' }, stream_entry.content)
+        xml.title
+
+        xml['activity'].send('verb', "http://activitystrea.ms/schema/1.0/#{stream_entry.verb}")
+        xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{stream_entry.object_type}")
+      end
+    end
+  end
+end.to_xml
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..862374a98f26c7234c8e48d402863869e0bde381
--- /dev/null
+++ b/app/views/home/index.html.haml
@@ -0,0 +1 @@
+Mastodon
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
deleted file mode 100644
index ff0d4c8653855c3319f27baeb7ff555db0a70ebb..0000000000000000000000000000000000000000
--- a/app/views/layouts/application.html.erb
+++ /dev/null
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-  <title>Mastodon</title>
-  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track' => true %>
-  <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
-  <%= csrf_meta_tags %>
-</head>
-<body>
-
-<%= yield %>
-
-</body>
-</html>
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..0cb4e96e8fd3be740c5ad08f84574053bc9bd9db
--- /dev/null
+++ b/app/views/layouts/application.html.haml
@@ -0,0 +1,10 @@
+!!!
+%html
+  %head
+    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
+    %title Mastodon
+    = stylesheet_link_tag    'application', media: 'all'
+    = javascript_include_tag 'application'
+    = csrf_meta_tags
+  %body
+    = yield
diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml
new file mode 100644
index 0000000000000000000000000000000000000000..dcb5764ec0a13e55814b0cdbf886f98b68406917
--- /dev/null
+++ b/app/views/profile/show.html.haml
@@ -0,0 +1,2 @@
+%h1 Profile#show
+%p Find me in app/views/profile/show.html.haml
diff --git a/app/views/xrd/host_meta.xml.ruby b/app/views/xrd/host_meta.xml.ruby
new file mode 100644
index 0000000000000000000000000000000000000000..07d0264711348d3f103c1b295ead884751c6ae34
--- /dev/null
+++ b/app/views/xrd/host_meta.xml.ruby
@@ -0,0 +1,5 @@
+Nokogiri::XML::Builder.new do |xml|
+  xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
+    xml.Link(rel: 'lrdd', type: 'application/xrd+xml', template: @webfinger_template)
+  end
+end.to_xml
diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/xrd/webfinger.xml.ruby
new file mode 100644
index 0000000000000000000000000000000000000000..7a1e9a1d34625150285ae8b5424d1adf4c2c3f5b
--- /dev/null
+++ b/app/views/xrd/webfinger.xml.ruby
@@ -0,0 +1,8 @@
+Nokogiri::XML::Builder.new do |xml|
+  xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
+    xml.Subject @canonical_account_uri
+    xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
+    xml.Link(rel: 'salmon', href: salmon_url(@account))
+    xml.Link(rel: 'magic-public-key', href: @magic_key)
+  end
+end.to_xml
diff --git a/config/environments/development.rb b/config/environments/development.rb
index b55e2144b6b9ef10c12c1900302cb24a242ee7d2..c3377aac82e4d0285ddabce2fb1ccf13087a8d84 100644
--- a/config/environments/development.rb
+++ b/config/environments/development.rb
@@ -38,4 +38,6 @@ Rails.application.configure do
 
   # Raises error for missing translations
   # config.action_view.raise_on_missing_translations = true
+
+  config.action_mailer.default_url_options = { host: ENV['NGROK_HOST'] }
 end
diff --git a/config/initializers/ostatus.rb b/config/initializers/ostatus.rb
new file mode 100644
index 0000000000000000000000000000000000000000..64204870bb3d053cf80370a722127ce358d55593
--- /dev/null
+++ b/config/initializers/ostatus.rb
@@ -0,0 +1 @@
+LOCAL_DOMAIN = ENV['LOCAL_DOMAIN'] || 'localhost'
diff --git a/config/routes.rb b/config/routes.rb
index fed31a3028945314ee1abf9985f03c5561ae2fb8..9ddc00c8f599052abec559f2b1f933a75d17de3c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,3 +1,11 @@
 Rails.application.routes.draw do
+  get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta
+  get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger
+
+  get 'atom/:id',   to: 'atom#user_stream', as: :atom_user_stream
+  get 'user/:name', to: 'profile#show', as: :profile
+
   mount Mastodon::API => '/api/'
+
+  root 'home#index'
 end
diff --git a/db/migrate/20160221003140_create_users.rb b/db/migrate/20160221003140_create_users.rb
new file mode 100644
index 0000000000000000000000000000000000000000..c9750c6238bf1f716ebb8f3843f2d0206d4ea92f
--- /dev/null
+++ b/db/migrate/20160221003140_create_users.rb
@@ -0,0 +1,12 @@
+class CreateUsers < ActiveRecord::Migration
+  def change
+    create_table :users do |t|
+      t.string :email, null: false, default: ''
+      t.integer :account_id, null: false
+
+      t.timestamps null: false
+    end
+
+    add_index :users, :email, unique: true
+  end
+end
diff --git a/db/migrate/20160221003621_create_follows.rb b/db/migrate/20160221003621_create_follows.rb
new file mode 100644
index 0000000000000000000000000000000000000000..afec3dee031843c2893de9086c0754fabf590b42
--- /dev/null
+++ b/db/migrate/20160221003621_create_follows.rb
@@ -0,0 +1,12 @@
+class CreateFollows < ActiveRecord::Migration
+  def change
+    create_table :follows do |t|
+      t.integer :account_id, null: false
+      t.integer :target_account_id, null: false
+
+      t.timestamps null: false
+    end
+
+    add_index :follows, [:account_id, :target_account_id], unique: true
+  end
+end
diff --git a/db/migrate/20160222122600_create_stream_entries.rb b/db/migrate/20160222122600_create_stream_entries.rb
new file mode 100644
index 0000000000000000000000000000000000000000..10a6862d9f64c3b50a288c69def1201ae595f9b1
--- /dev/null
+++ b/db/migrate/20160222122600_create_stream_entries.rb
@@ -0,0 +1,11 @@
+class CreateStreamEntries < ActiveRecord::Migration
+  def change
+    create_table :stream_entries do |t|
+      t.integer :account_id
+      t.integer :activity_id
+      t.string :activity_type
+
+      t.timestamps null: false
+    end
+  end
+end
diff --git a/db/migrate/20160222143943_add_profile_fields_to_accounts.rb b/db/migrate/20160222143943_add_profile_fields_to_accounts.rb
new file mode 100644
index 0000000000000000000000000000000000000000..221142bdd4b79735ce609f7d8f8b49cd9ddc9e18
--- /dev/null
+++ b/db/migrate/20160222143943_add_profile_fields_to_accounts.rb
@@ -0,0 +1,7 @@
+class AddProfileFieldsToAccounts < ActiveRecord::Migration
+  def change
+    add_column :accounts, :note, :text, null: false, default: ''
+    add_column :accounts, :display_name, :string, null: false, default: ''
+    add_column :accounts, :uri, :string, null: false, default: ''
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 49ba23f19bf0d589c3e4cd6ee812d99abdef0882..7cd7c371dde711c64e25ef277a50885aee89b5f0 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160220211917) do
+ActiveRecord::Schema.define(version: 20160222143943) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -28,10 +28,22 @@ ActiveRecord::Schema.define(version: 20160220211917) do
     t.string   "hub_url",      default: "", null: false
     t.datetime "created_at",                null: false
     t.datetime "updated_at",                null: false
+    t.text     "note",         default: "", null: false
+    t.string   "display_name", default: "", null: false
+    t.string   "uri",          default: "", null: false
   end
 
   add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
 
+  create_table "follows", force: :cascade do |t|
+    t.integer  "account_id",        null: false
+    t.integer  "target_account_id", null: false
+    t.datetime "created_at",        null: false
+    t.datetime "updated_at",        null: false
+  end
+
+  add_index "follows", ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true, using: :btree
+
   create_table "statuses", force: :cascade do |t|
     t.string   "uri",        default: "", null: false
     t.integer  "account_id",              null: false
@@ -42,4 +54,21 @@ ActiveRecord::Schema.define(version: 20160220211917) do
 
   add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
 
+  create_table "stream_entries", force: :cascade do |t|
+    t.integer  "account_id"
+    t.integer  "activity_id"
+    t.string   "activity_type"
+    t.datetime "created_at",    null: false
+    t.datetime "updated_at",    null: false
+  end
+
+  create_table "users", force: :cascade do |t|
+    t.string   "email",      default: "", null: false
+    t.integer  "account_id",              null: false
+    t.datetime "created_at",              null: false
+    t.datetime "updated_at",              null: false
+  end
+
+  add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+
 end
diff --git a/spec/controllers/atom_controller_spec.rb b/spec/controllers/atom_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ec14db007ba467a453c502f5d7b88b7cf861e60b
--- /dev/null
+++ b/spec/controllers/atom_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AtomController, type: :controller do
+
+end
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e672b25e4d45892abff11664cd9fac8c26df975c
--- /dev/null
+++ b/spec/controllers/home_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe HomeController, type: :controller do
+
+end
diff --git a/spec/controllers/profile_controller_spec.rb b/spec/controllers/profile_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..5904f5140ea4ea7a074d16df60e2d4b22ce90f48
--- /dev/null
+++ b/spec/controllers/profile_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe ProfileController, type: :controller do
+
+  describe "GET #show" do
+    it "returns http success" do
+      get :show
+      expect(response).to have_http_status(:success)
+    end
+  end
+
+end
diff --git a/spec/controllers/xrd_controller_spec.rb b/spec/controllers/xrd_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..03a4b580096a5b6f139f99121645a124bc7794dd
--- /dev/null
+++ b/spec/controllers/xrd_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe XrdController, type: :controller do
+
+end
diff --git a/spec/helpers/atom_helper_spec.rb b/spec/helpers/atom_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..57b12de67382dc6892c9b47e3520891d91abe1d3
--- /dev/null
+++ b/spec/helpers/atom_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the AtomHelper. For example:
+#
+# describe AtomHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe AtomHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e537d8d9a85f28a5a2745544c0763a37fc54177a
--- /dev/null
+++ b/spec/helpers/home_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the HomeHelper. For example:
+#
+# describe HomeHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe HomeHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/profile_helper_spec.rb b/spec/helpers/profile_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..154c7dfb7458641d38007b1d7f4d215b5005e7ea
--- /dev/null
+++ b/spec/helpers/profile_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the ProfileHelper. For example:
+#
+# describe ProfileHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe ProfileHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/xrd_helper_spec.rb b/spec/helpers/xrd_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..63ca2268a4b75a9c5d9b12041aacf7c3c9f7f6e4
--- /dev/null
+++ b/spec/helpers/xrd_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the XrdHelper. For example:
+#
+# describe XrdHelper do
+#   describe "string concat" do
+#     it "concats two strings with spaces" do
+#       expect(helper.concat_strings("this","that")).to eq("this that")
+#     end
+#   end
+# end
+RSpec.describe XrdHelper, type: :helper do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..9b76332f63ed543df0200e1b664ccc6f1c241595
--- /dev/null
+++ b/spec/models/follow_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Follow, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/stream_spec.rb b/spec/models/stream_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7fc77565285e5fb2030af7707c7f512af4d18515
--- /dev/null
+++ b/spec/models/stream_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Stream, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..47a31bb435500313e6f5e5bd5e353727a21152a6
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe User, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/views/profile/show.html.haml_spec.rb b/spec/views/profile/show.html.haml_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..778dcff323ba4cfcb8217011f9f81721fb5bb269
--- /dev/null
+++ b/spec/views/profile/show.html.haml_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe "profile/show.html.haml", type: :view do
+  pending "add some examples to (or delete) #{__FILE__}"
+end