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:
		| @@ -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       = '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 | ||||
							
								
								
									
										13
									
								
								db/schema.rb
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								db/schema.rb
									
									
									
									
									
								
							| @@ -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" | ||||
| @@ -94,15 +94,16 @@ ActiveRecord::Schema.define(version: 20160325130944) do | ||||
|   end | ||||
|  | ||||
|   create_table "oauth_applications", force: :cascade do |t| | ||||
|     t.string   "name",                      null: false | ||||
|     t.string   "uid",                       null: false | ||||
|     t.string   "secret",                    null: false | ||||
|     t.text     "redirect_uri",              null: false | ||||
|     t.string   "scopes",       default: "", null: false | ||||
|     t.string   "name",                         null: false | ||||
|     t.string   "uid",                          null: false | ||||
|     t.string   "secret",                       null: false | ||||
|     t.text     "redirect_uri",                 null: false | ||||
|     t.string   "scopes",       default: "",    null: false | ||||
|     t.datetime "created_at" | ||||
|     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", | ||||
|   | ||||
		Reference in New Issue
	
	Block a user