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 nowpull/69/head
parent
44e57f64dd
commit
92afd29650
|
@ -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|
|
skip_authorization do |resource_owner, client|
|
||||||
# client.superapp? or resource_owner.admin?
|
client.superapp?
|
||||||
# end
|
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
|
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.
|
# 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 "name", null: false
|
||||||
t.string "uid", null: false
|
t.string "uid", null: false
|
||||||
t.string "secret", null: false
|
t.string "secret", null: false
|
||||||
t.text "redirect_uri", null: false
|
t.text "redirect_uri", null: false
|
||||||
t.string "scopes", default: "", 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.
|
web_app = Doorkeeper::Application.new(name: 'Web', superapp: true, redirect_uri: Doorkeeper.configuration.native_redirect_uri)
|
||||||
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
|
web_app.save(validate: false)
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
#
|
|
||||||
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
|
|
||||||
# Mayor.create(name: 'Emanuel', city: cities.first)
|
|
||||||
|
|
|
@ -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",
|
||||||
|
|
Loading…
Reference in New Issue