The frontend will now be an OAuth app, auto-authorized. The frontend will use an access token for API requests
Adding better errors for the API controllers, posting a simple status works from the frontend now
This commit is contained in:
		
							parent
							
								
									44e57f64dd
								
							
						
					
					
						commit
						92afd29650
					
				@ -9,9 +9,9 @@ WORKDIR /mastodon
 | 
			
		||||
 | 
			
		||||
ADD Gemfile /mastodon/Gemfile
 | 
			
		||||
ADD Gemfile.lock /mastodon/Gemfile.lock
 | 
			
		||||
ADD package.json /mastodon/package.json
 | 
			
		||||
 | 
			
		||||
RUN bundle install --deployment --without test development
 | 
			
		||||
 | 
			
		||||
ADD package.json /mastodon/package.json
 | 
			
		||||
RUN npm install
 | 
			
		||||
 | 
			
		||||
ADD . /mastodon
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								app/assets/javascripts/components/actions/meta.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/assets/javascripts/components/actions/meta.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN';
 | 
			
		||||
 | 
			
		||||
export function setAccessToken(token) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: SET_ACCESS_TOKEN,
 | 
			
		||||
    token: token
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,11 @@ import fetch from 'isomorphic-fetch'
 | 
			
		||||
 | 
			
		||||
export const SET_TIMELINE = 'SET_TIMELINE';
 | 
			
		||||
export const ADD_STATUS   = 'ADD_STATUS';
 | 
			
		||||
 | 
			
		||||
export const PUBLISH       = 'PUBLISH';
 | 
			
		||||
export const PUBLISH_START = 'PUBLISH_START';
 | 
			
		||||
export const PUBLISH_SUCC  = 'PUBLISH_SUCC';
 | 
			
		||||
export const PUBLISH_ERROR = 'PUBLISH_ERROR';
 | 
			
		||||
 | 
			
		||||
export function setTimeline(timeline, statuses) {
 | 
			
		||||
  return {
 | 
			
		||||
@ -20,14 +24,58 @@ export function addStatus(timeline, status) {
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function publishStart() {
 | 
			
		||||
  return {
 | 
			
		||||
    type: PUBLISH_START
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function publishError(error) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: PUBLISH_ERROR,
 | 
			
		||||
    error: error
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function publishSucc(status) {
 | 
			
		||||
  return {
 | 
			
		||||
    type: PUBLISH_SUCC,
 | 
			
		||||
    status: status
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function publish(text, in_reply_to_id) {
 | 
			
		||||
  return function (dispatch) {
 | 
			
		||||
  return function (dispatch, getState) {
 | 
			
		||||
    const access_token = getState().getIn(['meta', 'access_token']);
 | 
			
		||||
 | 
			
		||||
    var data = new FormData();
 | 
			
		||||
 | 
			
		||||
    data.append('status', text);
 | 
			
		||||
 | 
			
		||||
    if (in_reply_to_id !== null) {
 | 
			
		||||
      data.append('in_reply_to_id', in_reply_to_id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    dispatch(publishStart());
 | 
			
		||||
 | 
			
		||||
    return fetch('/api/statuses', {
 | 
			
		||||
      method: 'POST'
 | 
			
		||||
      method: 'POST',
 | 
			
		||||
 | 
			
		||||
      headers: {
 | 
			
		||||
        'Authorization': `Bearer ${access_token}`
 | 
			
		||||
      },
 | 
			
		||||
 | 
			
		||||
      body: data
 | 
			
		||||
    }).then(function (response) {
 | 
			
		||||
      return response.json();
 | 
			
		||||
    }).then(function (json) {
 | 
			
		||||
      console.log(json);
 | 
			
		||||
      if (json.error) {
 | 
			
		||||
        dispatch(publishError(json.error));
 | 
			
		||||
      } else {
 | 
			
		||||
        dispatch(publishSucc(json));
 | 
			
		||||
      }
 | 
			
		||||
    }).catch(function (error) {
 | 
			
		||||
      dispatch(publishError(error));
 | 
			
		||||
    });
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -26,6 +26,7 @@ const ComposerDrawer = React.createClass({
 | 
			
		||||
 | 
			
		||||
  handleSubmit () {
 | 
			
		||||
    this.props.onSubmit(this.state.text, null);
 | 
			
		||||
    this.setState({ text: '' });
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  render () {
 | 
			
		||||
 | 
			
		||||
@ -2,12 +2,23 @@ import { Provider }               from 'react-redux';
 | 
			
		||||
import configureStore             from '../store/configureStore';
 | 
			
		||||
import Frontend                   from '../components/frontend';
 | 
			
		||||
import { setTimeline, addStatus } from '../actions/statuses';
 | 
			
		||||
import { setAccessToken }         from '../actions/meta';
 | 
			
		||||
import PureRenderMixin            from 'react-addons-pure-render-mixin';
 | 
			
		||||
 | 
			
		||||
const store = configureStore();
 | 
			
		||||
 | 
			
		||||
const Root = React.createClass({
 | 
			
		||||
 | 
			
		||||
  propTypes: {
 | 
			
		||||
    token: React.PropTypes.string.isRequired,
 | 
			
		||||
    timelines: React.PropTypes.array
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  mixins: [PureRenderMixin],
 | 
			
		||||
 | 
			
		||||
  componentWillMount() {
 | 
			
		||||
    store.dispatch(setAccessToken(this.props.token));
 | 
			
		||||
 | 
			
		||||
    for (var timelineType in this.props.timelines) {
 | 
			
		||||
      if (this.props.timelines.hasOwnProperty(timelineType)) {
 | 
			
		||||
        store.dispatch(setTimeline(timelineType, JSON.parse(this.props.timelines[timelineType])));
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,8 @@
 | 
			
		||||
import { combineReducers } from 'redux-immutable';
 | 
			
		||||
import statuses            from './statuses';
 | 
			
		||||
import meta                from './meta';
 | 
			
		||||
 | 
			
		||||
export default combineReducers({
 | 
			
		||||
  statuses
 | 
			
		||||
  statuses,
 | 
			
		||||
  meta
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										13
									
								
								app/assets/javascripts/components/reducers/meta.jsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								app/assets/javascripts/components/reducers/meta.jsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
import { SET_ACCESS_TOKEN }         from '../actions/meta';
 | 
			
		||||
import Immutable                    from 'immutable';
 | 
			
		||||
 | 
			
		||||
const initialState = Immutable.Map();
 | 
			
		||||
 | 
			
		||||
export default function meta(state = initialState, action) {
 | 
			
		||||
  switch(action.type) {
 | 
			
		||||
    case SET_ACCESS_TOKEN:
 | 
			
		||||
      return state.set('access_token', action.token);
 | 
			
		||||
    default:
 | 
			
		||||
      return state;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,14 @@ class ApiController < ApplicationController
 | 
			
		||||
  protect_from_forgery with: :null_session
 | 
			
		||||
  skip_before_action :verify_authenticity_token
 | 
			
		||||
 | 
			
		||||
  rescue_from ActiveRecord::RecordInvalid do
 | 
			
		||||
    render json: { error: 'Record invalid' }, status: 422
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  rescue_from ActiveRecord::RecordNotFound do
 | 
			
		||||
    render json: { error: 'Record not found' }, status: 404
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def current_resource_owner
 | 
			
		||||
 | 
			
		||||
@ -5,5 +5,12 @@ class HomeController < ApplicationController
 | 
			
		||||
    @body_classes = 'app-body'
 | 
			
		||||
    @home         = Feed.new(:home, current_user.account).get(20)
 | 
			
		||||
    @mentions     = Feed.new(:mentions, current_user.account).get(20)
 | 
			
		||||
    @token        = find_or_create_access_token.token
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  private
 | 
			
		||||
 | 
			
		||||
  def find_or_create_access_token
 | 
			
		||||
    Doorkeeper::AccessToken.find_or_create_for(Doorkeeper::Application.where(superapp: true).first, current_user.id, nil, Doorkeeper.configuration.access_token_expires_in, Doorkeeper.configuration.refresh_token_enabled?)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
= react_component 'Root', { timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false
 | 
			
		||||
= react_component 'Root', { token: @token, timelines: { home: render(file: 'api/statuses/home', locals: { statuses: @home }, formats: :json), mentions: render(file: 'api/statuses/mentions', locals: { statuses: @mentions }, formats: :json) }}, class: 'app-holder', prerender: false
 | 
			
		||||
 | 
			
		||||
@ -62,6 +62,8 @@ Rails.application.configure do
 | 
			
		||||
    Bullet.enable = true
 | 
			
		||||
    Bullet.bullet_logger = true
 | 
			
		||||
    Bullet.rails_logger = true
 | 
			
		||||
 | 
			
		||||
    Bullet.add_whitelist type: :n_plus_one_query, class_name: 'User', association: :account
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  config.react.variant = :development
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ Doorkeeper.configure do
 | 
			
		||||
 | 
			
		||||
  # This block will be called to check whether the resource owner is authenticated or not.
 | 
			
		||||
  resource_owner_authenticator do
 | 
			
		||||
    current_user || warden.authenticate!(scope: :user)
 | 
			
		||||
    current_user || redirect_to(new_user_session_url)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  resource_owner_from_credentials do |routes|
 | 
			
		||||
@ -100,9 +100,9 @@ Doorkeeper.configure do
 | 
			
		||||
  # Under some circumstances you might want to have applications auto-approved,
 | 
			
		||||
  # so that the user skips the authorization step.
 | 
			
		||||
  # For example if dealing with a trusted application.
 | 
			
		||||
  # skip_authorization do |resource_owner, client|
 | 
			
		||||
  #   client.superapp? or resource_owner.admin?
 | 
			
		||||
  # end
 | 
			
		||||
  skip_authorization do |resource_owner, client|
 | 
			
		||||
    client.superapp?
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  # WWW-Authenticate Realm (default "Doorkeeper").
 | 
			
		||||
  # realm "Doorkeeper"
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,5 @@
 | 
			
		||||
class AddSuperappToOauthApplications < ActiveRecord::Migration[5.0]
 | 
			
		||||
  def change
 | 
			
		||||
    add_column :oauth_applications, :superapp, :boolean, default: false, null: false
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
@ -10,7 +10,7 @@
 | 
			
		||||
#
 | 
			
		||||
# It's strongly recommended that you check this file into your version control system.
 | 
			
		||||
 | 
			
		||||
ActiveRecord::Schema.define(version: 20160325130944) do
 | 
			
		||||
ActiveRecord::Schema.define(version: 20160826155805) do
 | 
			
		||||
 | 
			
		||||
  # These are extensions that must be enabled in order to support this database
 | 
			
		||||
  enable_extension "plpgsql"
 | 
			
		||||
@ -103,6 +103,7 @@ ActiveRecord::Schema.define(version: 20160325130944) do
 | 
			
		||||
    t.datetime "updated_at"
 | 
			
		||||
    t.integer  "owner_id"
 | 
			
		||||
    t.string   "owner_type"
 | 
			
		||||
    t.boolean  "superapp",     default: false, null: false
 | 
			
		||||
    t.index ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
 | 
			
		||||
    t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,2 @@
 | 
			
		||||
# This file should contain all the record creation needed to seed the database with its default values.
 | 
			
		||||
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
 | 
			
		||||
#
 | 
			
		||||
# Examples:
 | 
			
		||||
#
 | 
			
		||||
#   cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
 | 
			
		||||
#   Mayor.create(name: 'Emanuel', city: cities.first)
 | 
			
		||||
web_app = Doorkeeper::Application.new(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri)
 | 
			
		||||
web_app.save(validate: false)
 | 
			
		||||
 | 
			
		||||
@ -15,6 +15,7 @@
 | 
			
		||||
    "immutable": "^3.8.1",
 | 
			
		||||
    "isomorphic-fetch": "^2.2.1",
 | 
			
		||||
    "moment": "^2.14.1",
 | 
			
		||||
    "react-addons-pure-render-mixin": "^15.3.1",
 | 
			
		||||
    "react-immutable-proptypes": "^2.1.0",
 | 
			
		||||
    "react-redux": "^4.4.5",
 | 
			
		||||
    "redux": "^3.5.2",
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user