Adding better errors for the API controllers, posting a simple status works from the frontend nowmaster
@@ -9,9 +9,9 @@ WORKDIR /mastodon | |||||
ADD Gemfile /mastodon/Gemfile | ADD Gemfile /mastodon/Gemfile | ||||
ADD Gemfile.lock /mastodon/Gemfile.lock | ADD Gemfile.lock /mastodon/Gemfile.lock | ||||
ADD package.json /mastodon/package.json | |||||
RUN bundle install --deployment --without test development | RUN bundle install --deployment --without test development | ||||
ADD package.json /mastodon/package.json | |||||
RUN npm install | RUN npm install | ||||
ADD . /mastodon | ADD . /mastodon | ||||
@@ -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 SET_TIMELINE = 'SET_TIMELINE'; | ||||
export const ADD_STATUS = 'ADD_STATUS'; | 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) { | export function setTimeline(timeline, statuses) { | ||||
return { | 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) { | 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', { | return fetch('/api/statuses', { | ||||
method: 'POST' | |||||
method: 'POST', | |||||
headers: { | |||||
'Authorization': `Bearer ${access_token}` | |||||
}, | |||||
body: data | |||||
}).then(function (response) { | }).then(function (response) { | ||||
return response.json(); | return response.json(); | ||||
}).then(function (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 () { | handleSubmit () { | ||||
this.props.onSubmit(this.state.text, null); | this.props.onSubmit(this.state.text, null); | ||||
this.setState({ text: '' }); | |||||
}, | }, | ||||
render () { | render () { | ||||
@@ -2,12 +2,23 @@ import { Provider } from 'react-redux'; | |||||
import configureStore from '../store/configureStore'; | import configureStore from '../store/configureStore'; | ||||
import Frontend from '../components/frontend'; | import Frontend from '../components/frontend'; | ||||
import { setTimeline, addStatus } from '../actions/statuses'; | import { setTimeline, addStatus } from '../actions/statuses'; | ||||
import { setAccessToken } from '../actions/meta'; | |||||
import PureRenderMixin from 'react-addons-pure-render-mixin'; | |||||
const store = configureStore(); | const store = configureStore(); | ||||
const Root = React.createClass({ | const Root = React.createClass({ | ||||
propTypes: { | |||||
token: React.PropTypes.string.isRequired, | |||||
timelines: React.PropTypes.array | |||||
}, | |||||
mixins: [PureRenderMixin], | |||||
componentWillMount() { | componentWillMount() { | ||||
store.dispatch(setAccessToken(this.props.token)); | |||||
for (var timelineType in this.props.timelines) { | for (var timelineType in this.props.timelines) { | ||||
if (this.props.timelines.hasOwnProperty(timelineType)) { | if (this.props.timelines.hasOwnProperty(timelineType)) { | ||||
store.dispatch(setTimeline(timelineType, JSON.parse(this.props.timelines[timelineType]))); | store.dispatch(setTimeline(timelineType, JSON.parse(this.props.timelines[timelineType]))); | ||||
@@ -1,6 +1,8 @@ | |||||
import { combineReducers } from 'redux-immutable'; | import { combineReducers } from 'redux-immutable'; | ||||
import statuses from './statuses'; | import statuses from './statuses'; | ||||
import meta from './meta'; | |||||
export default combineReducers({ | export default combineReducers({ | ||||
statuses | |||||
statuses, | |||||
meta | |||||
}); | }); |
@@ -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 | protect_from_forgery with: :null_session | ||||
skip_before_action :verify_authenticity_token | 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 | protected | ||||
def current_resource_owner | def current_resource_owner | ||||
@@ -5,5 +5,12 @@ class HomeController < ApplicationController | |||||
@body_classes = 'app-body' | @body_classes = 'app-body' | ||||
@home = Feed.new(:home, current_user.account).get(20) | @home = Feed.new(:home, current_user.account).get(20) | ||||
@mentions = Feed.new(:mentions, 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 | ||||
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.enable = true | ||||
Bullet.bullet_logger = true | Bullet.bullet_logger = true | ||||
Bullet.rails_logger = true | Bullet.rails_logger = true | ||||
Bullet.add_whitelist type: :n_plus_one_query, class_name: 'User', association: :account | |||||
end | end | ||||
config.react.variant = :development | 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. | # This block will be called to check whether the resource owner is authenticated or not. | ||||
resource_owner_authenticator do | resource_owner_authenticator do | ||||
current_user || warden.authenticate!(scope: :user) | |||||
current_user || redirect_to(new_user_session_url) | |||||
end | end | ||||
resource_owner_from_credentials do |routes| | resource_owner_from_credentials do |routes| | ||||
@@ -100,9 +100,9 @@ Doorkeeper.configure do | |||||
# Under some circumstances you might want to have applications auto-approved, | # Under some circumstances you might want to have applications auto-approved, | ||||
# so that the user skips the authorization step. | # so that the user skips the authorization step. | ||||
# For example if dealing with a trusted application. | # 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"). | # WWW-Authenticate Realm (default "Doorkeeper"). | ||||
# realm "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. | # 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 | # These are extensions that must be enabled in order to support this database | ||||
enable_extension "plpgsql" | enable_extension "plpgsql" | ||||
@@ -94,15 +94,16 @@ ActiveRecord::Schema.define(version: 20160325130944) do | |||||
end | end | ||||
create_table "oauth_applications", force: :cascade do |t| | 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 "created_at" | ||||
t.datetime "updated_at" | t.datetime "updated_at" | ||||
t.integer "owner_id" | t.integer "owner_id" | ||||
t.string "owner_type" | 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 ["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 | t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree | ||||
end | 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", | "immutable": "^3.8.1", | ||||
"isomorphic-fetch": "^2.2.1", | "isomorphic-fetch": "^2.2.1", | ||||
"moment": "^2.14.1", | "moment": "^2.14.1", | ||||
"react-addons-pure-render-mixin": "^15.3.1", | |||||
"react-immutable-proptypes": "^2.1.0", | "react-immutable-proptypes": "^2.1.0", | ||||
"react-redux": "^4.4.5", | "react-redux": "^4.4.5", | ||||
"redux": "^3.5.2", | "redux": "^3.5.2", | ||||