about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-03-07 12:42:33 +0100
committerEugen Rochko <eugen@zeonfederated.com>2016-03-07 12:42:33 +0100
commitab6696e855b58cdb2b6264c9acb0397dd7384e25 (patch)
tree516e8c8f3a574e669d9f5b192655c54a7bcda38b /app
parent3824c588533f481011d2be19ff9476c001ffbee9 (diff)
Adding doorkeeper, adding a REST API
POST /api/statuses                  Params: status (text contents), in_reply_to_id (optional)
GET  /api/statuses/:id
POST /api/statuses/:id/reblog

GET  /api/accounts/:id
GET  /api/accounts/:id/following
GET  /api/accounts/:id/followers
POST /api/accounts/:id/follow
POST /api/accounts/:id/unfollow

POST /api/follows                  Params: uri (e.g. user@domain)

OAuth authentication is currently disabled, but the API can be used with HTTP Auth.
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/api/accounts.coffee3
-rw-r--r--app/assets/javascripts/api/follows.coffee3
-rw-r--r--app/assets/javascripts/api/statuses.coffee3
-rw-r--r--app/assets/stylesheets/api/accounts.scss3
-rw-r--r--app/assets/stylesheets/api/follows.scss3
-rw-r--r--app/assets/stylesheets/api/statuses.scss3
-rw-r--r--app/controllers/accounts_controller.rb11
-rw-r--r--app/controllers/api/accounts_controller.rb36
-rw-r--r--app/controllers/api/follows_controller.rb9
-rw-r--r--app/controllers/api/statuses_controller.rb18
-rw-r--r--app/controllers/api_controller.rb10
-rw-r--r--app/controllers/stream_entries_controller.rb16
-rw-r--r--app/helpers/api/accounts_helper.rb2
-rw-r--r--app/helpers/api/follows_helper.rb2
-rw-r--r--app/helpers/api/statuses_helper.rb2
-rw-r--r--app/helpers/stream_entries_helper.rb4
-rw-r--r--app/models/account.rb10
-rw-r--r--app/services/follow_service.rb5
-rw-r--r--app/views/api/accounts/followers.rabl2
-rw-r--r--app/views/api/accounts/following.rabl2
-rw-r--r--app/views/api/accounts/show.rabl9
-rw-r--r--app/views/api/accounts/statuses.rabl2
-rw-r--r--app/views/api/follows/show.rabl5
-rw-r--r--app/views/api/statuses/show.rabl18
-rw-r--r--app/views/doorkeeper/applications/_delete_form.html.erb5
-rw-r--r--app/views/doorkeeper/applications/_form.html.erb47
-rw-r--r--app/views/doorkeeper/applications/edit.html.erb5
-rw-r--r--app/views/doorkeeper/applications/index.html.erb26
-rw-r--r--app/views/doorkeeper/applications/new.html.erb5
-rw-r--r--app/views/doorkeeper/applications/show.html.erb39
-rw-r--r--app/views/doorkeeper/authorizations/error.html.erb7
-rw-r--r--app/views/doorkeeper/authorizations/new.html.erb40
-rw-r--r--app/views/doorkeeper/authorizations/show.html.erb7
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.erb5
-rw-r--r--app/views/doorkeeper/authorized_applications/index.html.erb25
-rw-r--r--app/views/layouts/doorkeeper/admin.html.erb37
-rw-r--r--app/views/layouts/doorkeeper/application.html.erb23
37 files changed, 420 insertions, 32 deletions
diff --git a/app/assets/javascripts/api/accounts.coffee b/app/assets/javascripts/api/accounts.coffee
new file mode 100644
index 000000000..24f83d18b
--- /dev/null
+++ b/app/assets/javascripts/api/accounts.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/api/follows.coffee b/app/assets/javascripts/api/follows.coffee
new file mode 100644
index 000000000..24f83d18b
--- /dev/null
+++ b/app/assets/javascripts/api/follows.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/api/statuses.coffee b/app/assets/javascripts/api/statuses.coffee
new file mode 100644
index 000000000..24f83d18b
--- /dev/null
+++ b/app/assets/javascripts/api/statuses.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/api/accounts.scss b/app/assets/stylesheets/api/accounts.scss
new file mode 100644
index 000000000..614f23d34
--- /dev/null
+++ b/app/assets/stylesheets/api/accounts.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Api::Accounts 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/api/follows.scss b/app/assets/stylesheets/api/follows.scss
new file mode 100644
index 000000000..4da2e7e2c
--- /dev/null
+++ b/app/assets/stylesheets/api/follows.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the API::Follows 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/api/statuses.scss b/app/assets/stylesheets/api/statuses.scss
new file mode 100644
index 000000000..dfa3227d8
--- /dev/null
+++ b/app/assets/stylesheets/api/statuses.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the API::Statuses 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/accounts_controller.rb b/app/controllers/accounts_controller.rb
index 9e2e160b2..156926927 100644
--- a/app/controllers/accounts_controller.rb
+++ b/app/controllers/accounts_controller.rb
@@ -3,7 +3,6 @@ class AccountsController < ApplicationController
 
   before_action :set_account
   before_action :set_webfinger_header
-  before_action :authenticate_user!, only: [:follow, :unfollow]
 
   def show
     @statuses = @account.statuses.order('id desc').includes(thread: [:account], reblog: [:account], stream_entry: [])
@@ -14,16 +13,6 @@ class AccountsController < ApplicationController
     end
   end
 
-  def follow
-    current_user.account.follow!(@account)
-    redirect_to root_path
-  end
-
-  def unfollow
-    current_user.account.unfollow!(@account)
-    redirect_to root_path
-  end
-
   private
 
   def set_account
diff --git a/app/controllers/api/accounts_controller.rb b/app/controllers/api/accounts_controller.rb
new file mode 100644
index 000000000..927fd86b7
--- /dev/null
+++ b/app/controllers/api/accounts_controller.rb
@@ -0,0 +1,36 @@
+class Api::AccountsController < ApiController
+  before_action :set_account
+  before_action :authenticate_user!
+  respond_to    :json
+
+  def show
+  end
+
+  def following
+    @following = @account.following
+  end
+
+  def followers
+    @followers = @account.followers
+  end
+
+  def statuses
+    @statuses = @account.statuses
+  end
+
+  def follow
+    @follow = current_user.account.follow!(@account)
+    render action: :show
+  end
+
+  def unfollow
+    @unfollow = current_user.account.unfollow!(@account)
+    render action: :show
+  end
+
+  private
+
+  def set_account
+    @account = Account.find(params[:id])
+  end
+end
diff --git a/app/controllers/api/follows_controller.rb b/app/controllers/api/follows_controller.rb
new file mode 100644
index 000000000..acf627a07
--- /dev/null
+++ b/app/controllers/api/follows_controller.rb
@@ -0,0 +1,9 @@
+class Api::FollowsController < ApiController
+  before_action :authenticate_user!
+  respond_to    :json
+
+  def create
+    @follow = FollowService.new.(current_user.account, params[:uri])
+    render action: :show
+  end
+end
diff --git a/app/controllers/api/statuses_controller.rb b/app/controllers/api/statuses_controller.rb
new file mode 100644
index 000000000..872558f8e
--- /dev/null
+++ b/app/controllers/api/statuses_controller.rb
@@ -0,0 +1,18 @@
+class Api::StatusesController < ApiController
+  before_action :authenticate_user!
+  respond_to    :json
+
+  def show
+    @status = Status.find(params[:id])
+  end
+
+  def create
+    @status = PostStatusService.new.(current_user.account, params[:status], params[:in_reply_to_id].blank? ? nil : Status.find(params[:in_reply_to_id]))
+    render action: :show
+  end
+
+  def reblog
+    @status = ReblogService.new.(current_user.account, Status.find(params[:id]))
+    render action: :show
+  end
+end
diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb
index eb2e464eb..d24f63f27 100644
--- a/app/controllers/api_controller.rb
+++ b/app/controllers/api_controller.rb
@@ -1,3 +1,13 @@
 class ApiController < ApplicationController
   protect_from_forgery with: :null_session
+
+  protected
+
+  def current_resource_owner
+    User.find(doorkeeper_token.user_id) if doorkeeper_token
+  end
+
+  def current_user
+    super || current_resource_owner
+  end
 end
diff --git a/app/controllers/stream_entries_controller.rb b/app/controllers/stream_entries_controller.rb
index 293cc6d81..cbf7bfdff 100644
--- a/app/controllers/stream_entries_controller.rb
+++ b/app/controllers/stream_entries_controller.rb
@@ -3,8 +3,6 @@ class StreamEntriesController < ApplicationController
 
   before_action :set_account
   before_action :set_stream_entry
-  before_action :authenticate_user!, only: [:reblog, :favourite]
-  before_action :only_statuses!, only: [:reblog, :favourite]
 
   def show
     @type = @stream_entry.activity_type.downcase
@@ -15,16 +13,6 @@ class StreamEntriesController < ApplicationController
     end
   end
 
-  def reblog
-    ReblogService.new.(current_user.account, @stream_entry.activity)
-    redirect_to root_path
-  end
-
-  def favourite
-    FavouriteService.new.(current_user.account, @stream_entry.activity)
-    redirect_to root_path
-  end
-
   private
 
   def set_account
@@ -34,8 +22,4 @@ class StreamEntriesController < ApplicationController
   def set_stream_entry
     @stream_entry = @account.stream_entries.find(params[:id])
   end
-
-  def only_statuses!
-    redirect_to root_url unless @stream_entry.activity_type == 'Status'
-  end
 end
diff --git a/app/helpers/api/accounts_helper.rb b/app/helpers/api/accounts_helper.rb
new file mode 100644
index 000000000..d9a54c7bc
--- /dev/null
+++ b/app/helpers/api/accounts_helper.rb
@@ -0,0 +1,2 @@
+module Api::AccountsHelper
+end
diff --git a/app/helpers/api/follows_helper.rb b/app/helpers/api/follows_helper.rb
new file mode 100644
index 000000000..d8022d93c
--- /dev/null
+++ b/app/helpers/api/follows_helper.rb
@@ -0,0 +1,2 @@
+module Api::FollowsHelper
+end
diff --git a/app/helpers/api/statuses_helper.rb b/app/helpers/api/statuses_helper.rb
new file mode 100644
index 000000000..3187f3e3b
--- /dev/null
+++ b/app/helpers/api/statuses_helper.rb
@@ -0,0 +1,2 @@
+module Api::StatusesHelper
+end
diff --git a/app/helpers/stream_entries_helper.rb b/app/helpers/stream_entries_helper.rb
index d6a14352f..2a59553ab 100644
--- a/app/helpers/stream_entries_helper.rb
+++ b/app/helpers/stream_entries_helper.rb
@@ -31,10 +31,10 @@ module StreamEntriesHelper
   end
 
   def reblogged_by_me_class(status)
-    user_signed_in? && (status.reblog? ? status.reblog : status).reblogs.where(account: current_user.account).count == 1 ? 'reblogged' : ''
+    user_signed_in? && current_user.account.reblogged?(status) ? 'reblogged' : ''
   end
 
   def favourited_by_me_class(status)
-    user_signed_in? && (status.reblog? ? status.reblog : status).favourites.where(account: current_user.account).count == 1 ? 'favourited' : ''
+    user_signed_in? && current_user.account.favourited?(status) ? 'favourited' : ''
   end
 end
diff --git a/app/models/account.rb b/app/models/account.rb
index 47e43f0ac..9e6dea4aa 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -24,7 +24,7 @@ class Account < ActiveRecord::Base
   MENTION_RE = /(?:^|\W)@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/i
 
   def follow!(other_account)
-    self.active_relationships.first_or_create!(target_account: other_account)
+    self.active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
   end
 
   def unfollow!(other_account)
@@ -59,6 +59,14 @@ class Account < ActiveRecord::Base
     !(self.secret.blank? || self.verify_token.blank?)
   end
 
+  def favourited?(status)
+    (status.reblog? ? status.reblog : status).favourites.where(account: self).count == 1
+  end
+
+  def reblogged?(status)
+    (status.reblog? ? status.reblog : status).reblogs.where(account: self).count == 1
+  end
+
   def keypair
     self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key)
   end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
index 785ae583f..0661c63f7 100644
--- a/app/services/follow_service.rb
+++ b/app/services/follow_service.rb
@@ -5,11 +5,12 @@ class FollowService < BaseService
   def call(source_account, uri)
     target_account = follow_remote_account_service.(uri)
 
-    return if target_account.nil?
+    return nil if target_account.nil?
 
     follow = source_account.follow!(target_account)
     send_interaction_service.(follow.stream_entry, target_account)
-    source_account.ping!(account_url(account, format: 'atom'), [Rails.configuration.x.hub_url])
+    source_account.ping!(account_url(source_account, format: 'atom'), [Rails.configuration.x.hub_url])
+    follow
   end
 
   private
diff --git a/app/views/api/accounts/followers.rabl b/app/views/api/accounts/followers.rabl
new file mode 100644
index 000000000..9bb0d9c8f
--- /dev/null
+++ b/app/views/api/accounts/followers.rabl
@@ -0,0 +1,2 @@
+collection @followers
+extends('api/accounts/show')
diff --git a/app/views/api/accounts/following.rabl b/app/views/api/accounts/following.rabl
new file mode 100644
index 000000000..9f2155293
--- /dev/null
+++ b/app/views/api/accounts/following.rabl
@@ -0,0 +1,2 @@
+collection @following
+extends('api/accounts/show')
diff --git a/app/views/api/accounts/show.rabl b/app/views/api/accounts/show.rabl
new file mode 100644
index 000000000..e4c4883c8
--- /dev/null
+++ b/app/views/api/accounts/show.rabl
@@ -0,0 +1,9 @@
+object @account
+
+attributes :id, :username, :acct, :display_name, :note
+
+node(:url)       { |account| url_for_target(account) }
+node(:avatar)    { |account| asset_url(account.avatar.url(:large, false)) }
+node(:followers) { |account| account.followers.count }
+node(:following) { |account| account.following.count }
+node(:statuses)  { |account| account.statuses.count  }
diff --git a/app/views/api/accounts/statuses.rabl b/app/views/api/accounts/statuses.rabl
new file mode 100644
index 000000000..12f00dd21
--- /dev/null
+++ b/app/views/api/accounts/statuses.rabl
@@ -0,0 +1,2 @@
+collection @statuses
+extends('api/statuses/show')
diff --git a/app/views/api/follows/show.rabl b/app/views/api/follows/show.rabl
new file mode 100644
index 000000000..38c3424da
--- /dev/null
+++ b/app/views/api/follows/show.rabl
@@ -0,0 +1,5 @@
+object @follow
+
+child :target_account => :target_account do
+  extends('api/accounts/show')
+end
diff --git a/app/views/api/statuses/show.rabl b/app/views/api/statuses/show.rabl
new file mode 100644
index 000000000..344517236
--- /dev/null
+++ b/app/views/api/statuses/show.rabl
@@ -0,0 +1,18 @@
+object @status
+attributes :id, :created_at, :in_reply_to_id
+
+node(:uri)        { |status| uri_for_target(status) }
+node(:content)    { |status| status.local? ? linkify(status) : status.content }
+node(:url)        { |status| url_for_target(status) }
+node(:reblogs)    { |status| status.reblogs.count }
+node(:favourites) { |status| status.favourites.count }
+node(:favourited) { |status| current_user.account.favourited?(status) }
+node(:reblogged)  { |status| current_user.account.reblogged?(status) }
+
+child :reblog => :reblog do
+  extends('api/statuses/show')
+end
+
+child :account do
+  extends('api/accounts/show')
+end
diff --git a/app/views/doorkeeper/applications/_delete_form.html.erb b/app/views/doorkeeper/applications/_delete_form.html.erb
new file mode 100644
index 000000000..8d8c93f87
--- /dev/null
+++ b/app/views/doorkeeper/applications/_delete_form.html.erb
@@ -0,0 +1,5 @@
+<%- submit_btn_css ||= 'btn btn-link' %>
+<%= form_tag oauth_application_path(application) do %>
+  <input type="hidden" name="_method" value="delete">
+  <%= submit_tag t('doorkeeper.applications.buttons.destroy'), onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')", class: submit_btn_css %>
+<% end %>
diff --git a/app/views/doorkeeper/applications/_form.html.erb b/app/views/doorkeeper/applications/_form.html.erb
new file mode 100644
index 000000000..f42cfdc10
--- /dev/null
+++ b/app/views/doorkeeper/applications/_form.html.erb
@@ -0,0 +1,47 @@
+<%= form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f| %>
+  <% if application.errors.any? %>
+    <div class="alert alert-danger" data-alert><p><%= t('doorkeeper.applications.form.error') %></p></div>
+  <% end %>
+
+  <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do %>
+    <%= f.label :name, class: 'col-sm-2 control-label' %>
+    <div class="col-sm-10">
+      <%= f.text_field :name, class: 'form-control' %>
+      <%= doorkeeper_errors_for application, :name %>
+    </div>
+  <% end %>
+
+  <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do %>
+    <%= f.label :redirect_uri, class: 'col-sm-2 control-label' %>
+    <div class="col-sm-10">
+      <%= f.text_area :redirect_uri, class: 'form-control' %>
+      <%= doorkeeper_errors_for application, :redirect_uri %>
+      <span class="help-block">
+        <%= t('doorkeeper.applications.help.redirect_uri') %>
+      </span>
+      <% if Doorkeeper.configuration.native_redirect_uri %>
+          <span class="help-block">
+            <%= raw t('doorkeeper.applications.help.native_redirect_uri', native_redirect_uri: "<code>#{ Doorkeeper.configuration.native_redirect_uri }</code>") %>
+          </span>
+      <% end %>
+    </div>
+  <% end %>
+
+  <%= content_tag :div, class: "form-group#{' has-error' if application.errors[:scopes].present?}" do %>
+    <%= f.label :scopes, class: 'col-sm-2 control-label' %>
+    <div class="col-sm-10">
+      <%= f.text_field :scopes, class: 'form-control' %>
+      <%= doorkeeper_errors_for application, :scopes %>
+      <span class="help-block">
+        <%= t('doorkeeper.applications.help.scopes') %>
+      </span>
+    </div>
+  <% end %>
+
+  <div class="form-group">
+    <div class="col-sm-offset-2 col-sm-10">
+      <%= f.submit t('doorkeeper.applications.buttons.submit'), class: "btn btn-primary" %>
+      <%= link_to t('doorkeeper.applications.buttons.cancel'), oauth_applications_path, :class => "btn btn-default" %>
+    </div>
+  </div>
+<% end %>
diff --git a/app/views/doorkeeper/applications/edit.html.erb b/app/views/doorkeeper/applications/edit.html.erb
new file mode 100644
index 000000000..05bddd2e4
--- /dev/null
+++ b/app/views/doorkeeper/applications/edit.html.erb
@@ -0,0 +1,5 @@
+<div class="page-header">
+  <h1><%= t('.title') %></h1>
+</div>
+
+<%= render 'form', application: @application %>
diff --git a/app/views/doorkeeper/applications/index.html.erb b/app/views/doorkeeper/applications/index.html.erb
new file mode 100644
index 000000000..4a3df8305
--- /dev/null
+++ b/app/views/doorkeeper/applications/index.html.erb
@@ -0,0 +1,26 @@
+<div class="page-header">
+  <h1><%= t('.title') %></h1>
+</div>
+
+<p><%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-success' %></p>
+
+<table class="table table-striped">
+  <thead>
+  <tr>
+    <th><%= t('.name') %></th>
+    <th><%= t('.callback_url') %></th>
+    <th></th>
+    <th></th>
+  </tr>
+  </thead>
+  <tbody>
+  <% @applications.each do |application| %>
+    <tr id="application_<%= application.id %>">
+      <td><%= link_to application.name, oauth_application_path(application) %></td>
+      <td><%= application.redirect_uri %></td>
+      <td><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %></td>
+      <td><%= render 'delete_form', application: application %></td>
+    </tr>
+  <% end %>
+  </tbody>
+</table>
diff --git a/app/views/doorkeeper/applications/new.html.erb b/app/views/doorkeeper/applications/new.html.erb
new file mode 100644
index 000000000..05bddd2e4
--- /dev/null
+++ b/app/views/doorkeeper/applications/new.html.erb
@@ -0,0 +1,5 @@
+<div class="page-header">
+  <h1><%= t('.title') %></h1>
+</div>
+
+<%= render 'form', application: @application %>
diff --git a/app/views/doorkeeper/applications/show.html.erb b/app/views/doorkeeper/applications/show.html.erb
new file mode 100644
index 000000000..ac89f32b1
--- /dev/null
+++ b/app/views/doorkeeper/applications/show.html.erb
@@ -0,0 +1,39 @@
+<div class="page-header">
+  <h1><%= t('.title', name: @application.name) %></h1>
+</div>
+
+<div class="row">
+  <div class="col-md-8">
+    <h4><%= t('.application_id') %>:</h4>
+    <p><code id="application_id"><%= @application.uid %></code></p>
+
+    <h4><%= t('.secret') %>:</h4>
+    <p><code id="secret"><%= @application.secret %></code></p>
+
+    <h4><%= t('.scopes') %>:</h4>
+    <p><code id="scopes"><%= @application.scopes %></code></p>
+
+    <h4><%= t('.callback_urls') %>:</h4>
+
+    <table>
+      <% @application.redirect_uri.split.each do |uri| %>
+        <tr>
+          <td>
+            <code><%= uri %></code>
+          </td>
+          <td>
+            <%= link_to t('doorkeeper.applications.buttons.authorize'), oauth_authorization_path(client_id: @application.uid, redirect_uri: uri, response_type: 'code'), class: 'btn btn-success', target: '_blank' %>
+          </td>
+        </tr>
+      <% end %>
+    </table>
+  </div>
+
+  <div class="col-md-4">
+    <h3><%= t('.actions') %></h3>
+
+    <p><%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(@application), class: 'btn btn-primary' %></p>
+
+    <p><%= render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger' %></p>
+  </div>
+</div>
diff --git a/app/views/doorkeeper/authorizations/error.html.erb b/app/views/doorkeeper/authorizations/error.html.erb
new file mode 100644
index 000000000..2247c0d54
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/error.html.erb
@@ -0,0 +1,7 @@
+<div class="page-header">
+  <h1><%= t('doorkeeper.authorizations.error.title') %></h1>
+</div>
+
+<main role="main">
+  <pre><%= @pre_auth.error_response.body[:error_description] %></pre>
+</main>
diff --git a/app/views/doorkeeper/authorizations/new.html.erb b/app/views/doorkeeper/authorizations/new.html.erb
new file mode 100644
index 000000000..c6f738b33
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/new.html.erb
@@ -0,0 +1,40 @@
+<header class="page-header" role="banner">
+  <h1><%= t('.title') %></h1>
+</header>
+
+<main role="main">
+  <p class="h4">
+    <%= raw t('.prompt', client_name: "<strong class=\"text-info\">#{ @pre_auth.client.name }</strong>") %>
+  </p>
+
+  <% if @pre_auth.scopes.count > 0 %>
+    <div id="oauth-permissions">
+      <p><%= t('.able_to') %>:</p>
+
+      <ul class="text-info">
+        <% @pre_auth.scopes.each do |scope| %>
+          <li><%= t scope, scope: [:doorkeeper, :scopes] %></li>
+        <% end %>
+      </ul>
+    </div>
+  <% end %>
+
+  <div class="actions">
+    <%= form_tag oauth_authorization_path, method: :post do %>
+      <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
+      <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
+      <%= hidden_field_tag :state, @pre_auth.state %>
+      <%= hidden_field_tag :response_type, @pre_auth.response_type %>
+      <%= hidden_field_tag :scope, @pre_auth.scope %>
+      <%= submit_tag t('doorkeeper.authorizations.buttons.authorize'), class: "btn btn-success btn-lg btn-block" %>
+    <% end %>
+    <%= form_tag oauth_authorization_path, method: :delete do %>
+      <%= hidden_field_tag :client_id, @pre_auth.client.uid %>
+      <%= hidden_field_tag :redirect_uri, @pre_auth.redirect_uri %>
+      <%= hidden_field_tag :state, @pre_auth.state %>
+      <%= hidden_field_tag :response_type, @pre_auth.response_type %>
+      <%= hidden_field_tag :scope, @pre_auth.scope %>
+      <%= submit_tag t('doorkeeper.authorizations.buttons.deny'), class: "btn btn-danger btn-lg btn-block" %>
+    <% end %>
+  </div>
+</main>
diff --git a/app/views/doorkeeper/authorizations/show.html.erb b/app/views/doorkeeper/authorizations/show.html.erb
new file mode 100644
index 000000000..f4d661019
--- /dev/null
+++ b/app/views/doorkeeper/authorizations/show.html.erb
@@ -0,0 +1,7 @@
+<header class="page-header">
+  <h1><%= t('.title') %>:</h1>
+</header>
+
+<main role="main">
+  <code id="authorization_code"><%= params[:code] %></code>
+</main>
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.erb b/app/views/doorkeeper/authorized_applications/_delete_form.html.erb
new file mode 100644
index 000000000..0b7e2dab3
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.erb
@@ -0,0 +1,5 @@
+<%- submit_btn_css ||= 'btn btn-link' %>
+<%= form_tag oauth_authorized_application_path(application) do %>
+  <input type="hidden" name="_method" value="delete">
+  <%= submit_tag t('doorkeeper.authorized_applications.buttons.revoke'), onclick: "return confirm('#{ t('doorkeeper.authorized_applications.confirmations.revoke') }')", class: submit_btn_css %>
+<% end %>
diff --git a/app/views/doorkeeper/authorized_applications/index.html.erb b/app/views/doorkeeper/authorized_applications/index.html.erb
new file mode 100644
index 000000000..aa8657c0f
--- /dev/null
+++ b/app/views/doorkeeper/authorized_applications/index.html.erb
@@ -0,0 +1,25 @@
+<header class="page-header">
+  <h1><%= t('doorkeeper.authorized_applications.index.title') %></h1>
+</header>
+
+<main role="main">
+  <table class="table table-striped">
+    <thead>
+    <tr>
+      <th><%= t('doorkeeper.authorized_applications.index.application') %></th>
+      <th><%= t('doorkeeper.authorized_applications.index.created_at') %></th>
+      <th></th>
+      <th></th>
+    </tr>
+    </thead>
+    <tbody>
+    <% @applications.each do |application| %>
+      <tr>
+        <td><%= application.name %></td>
+        <td><%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %></td>
+        <td><%= render 'delete_form', application: application %></td>
+      </tr>
+    <% end %>
+    </tbody>
+  </table>
+</main>
diff --git a/app/views/layouts/doorkeeper/admin.html.erb b/app/views/layouts/doorkeeper/admin.html.erb
new file mode 100644
index 000000000..1d1a688a2
--- /dev/null
+++ b/app/views/layouts/doorkeeper/admin.html.erb
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Doorkeeper</title>
+  <%= stylesheet_link_tag "doorkeeper/admin/application" %>
+  <%= csrf_meta_tags %>
+</head>
+<body>
+<div class="navbar navbar-inverse navbar-static-top" role="navigation">
+  <div class="container-fluid">
+    <div class="navbar-header">
+      <%= link_to t('doorkeeper.layouts.admin.nav.oauth2_provider'), oauth_applications_path, class: 'navbar-brand' %>
+    </div>
+    <ul class="nav navbar-nav">
+      <%= content_tag :li, class: "#{'active' if request.path == oauth_applications_path}" do %>
+        <%= link_to t('doorkeeper.layouts.admin.nav.applications'), oauth_applications_path %>
+      <% end %>
+      <%= content_tag :li do %>
+        <%= link_to 'Home', root_path %>
+      <% end %>
+    </ul>
+  </div>
+</div>
+<div class="container">
+  <%- if flash[:notice].present? %>
+    <div class="alert alert-info">
+      <%= flash[:notice] %>
+    </div>
+  <% end -%>
+
+  <%= yield %>
+</div>
+</body>
+</html>
diff --git a/app/views/layouts/doorkeeper/application.html.erb b/app/views/layouts/doorkeeper/application.html.erb
new file mode 100644
index 000000000..562005af0
--- /dev/null
+++ b/app/views/layouts/doorkeeper/application.html.erb
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title><%= t('doorkeeper.layouts.application.title') %></title>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+
+  <%= stylesheet_link_tag "doorkeeper/application" %>
+  <%= csrf_meta_tags %>
+</head>
+<body>
+<div id="container">
+  <%- if flash[:notice].present? %>
+    <div class="alert alert-info">
+      <%= flash[:notice] %>
+    </div>
+  <% end -%>
+
+  <%= yield %>
+</div>
+</body>
+</html>