diff --git a/Gemfile b/Gemfile index d89ee41..9e127e7 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ gem 'skylight' # application performance monitoring gem 'sprockets', '>= 3.0.0' gem 'uglifier' gem 'graph_matching' # max-weight matching algorithm used to pair +gem 'jsonapi-resources' # API is build around this gem # front-end libraries gem 'react_on_rails' diff --git a/Gemfile.lock b/Gemfile.lock index 21fb39e..e8a161c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -109,6 +109,9 @@ GEM hashdiff (0.3.2) i18n (0.8.1) json (2.1.0) + jsonapi-resources (0.8.3) + concurrent-ruby + rails (>= 4.0) lazy_priority_queue (0.1.1) libv8 (5.3.332.38.5) listen (3.1.5) @@ -300,6 +303,7 @@ DEPENDENCIES faker formulaic graph_matching + jsonapi-resources listen mini_racer normalize-rails diff --git a/app/controllers/api/application_controller.rb b/app/controllers/api/application_controller.rb new file mode 100644 index 0000000..c0e6e78 --- /dev/null +++ b/app/controllers/api/application_controller.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Api + class ApplicationController < JSONAPI::ResourceController + skip_before_action :ensure_correct_media_type + skip_before_action :ensure_valid_accept_media_type + protect_from_forgery with: :null_session + end +end diff --git a/app/controllers/api/debaters_controller.rb b/app/controllers/api/debaters_controller.rb new file mode 100644 index 0000000..51d84ca --- /dev/null +++ b/app/controllers/api/debaters_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module Api + class DebatersController < ApplicationController + end +end diff --git a/app/controllers/api/schools_controller.rb b/app/controllers/api/schools_controller.rb new file mode 100644 index 0000000..fe70ddc --- /dev/null +++ b/app/controllers/api/schools_controller.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +module Api + class SchoolsController < ApplicationController + end +end diff --git a/app/controllers/schools_controller.rb b/app/controllers/schools_controller.rb index 12fde9d..9f10ebf 100644 --- a/app/controllers/schools_controller.rb +++ b/app/controllers/schools_controller.rb @@ -2,50 +2,9 @@ class SchoolsController < ApplicationController def index @schools = School.order(:name).map(&:as_json) - respond_to do |format| - format.html { render :index } - format.json { render json: @schools } - end - end - - def create - @school = School.new(school_params) - if @school.save - render json: @school - else - render json: @school.errors, status: :unprocessable - end end def show @school = School.find(params[:id]).as_json - respond_to do |format| - format.html { render :show } - format.json { render json: @school } - end - end - - def update - @school = School.find(params[:id]) - if @school.update(school_params) - render json: @school - else - render json: @school.errors, status: :unprocessable - end - end - - def destroy - @school = School.find(params[:id]) - if @school.destroy - render json: {}, status: 200 - else - render json: {}, status: :unprocessable - end - end - - private - - def school_params - params.require(:school).permit(:name) end end diff --git a/app/resoucres/api/debater_resource.rb b/app/resoucres/api/debater_resource.rb new file mode 100644 index 0000000..8c34b85 --- /dev/null +++ b/app/resoucres/api/debater_resource.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Api + class DebaterResource < JSONAPI::Resource + attributes :name, :novice + belongs_to :team + belongs_to :school + end +end diff --git a/app/resoucres/api/school_resource.rb b/app/resoucres/api/school_resource.rb new file mode 100644 index 0000000..975750f --- /dev/null +++ b/app/resoucres/api/school_resource.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +module Api + class SchoolResource < JSONAPI::Resource + attributes :name + end +end diff --git a/app/resoucres/api/team_resource.rb b/app/resoucres/api/team_resource.rb new file mode 100644 index 0000000..2b4cc09 --- /dev/null +++ b/app/resoucres/api/team_resource.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Api + class TeamResource < JSONAPI::Resource + attributes :name, :seed + has_many :debaters + belongs_to :school + end +end diff --git a/client/app/components/debaters/DebaterContainer.js b/client/app/components/debaters/DebaterContainer.js index a3f2687..52ba473 100644 --- a/client/app/components/debaters/DebaterContainer.js +++ b/client/app/components/debaters/DebaterContainer.js @@ -1,7 +1,7 @@ import React from 'react' import {Button} from 'react-bootstrap' import {DebaterForm, DebaterDetail} from './index' -import Debater from '../../resources/Debater' +import {Debater, ApiDebater} from '../../resources/Debater' export class DebaterContainer extends React.Component { static propTypes = { @@ -35,11 +35,11 @@ export class DebaterContainer extends React.Component { handleEditClick = () => this.setState({ isEditing: true }) handleDeleteClick = () => { - const debater = new Debater(this.state.debater.id) + const debater = new ApiDebater(this.state.debater.id) const confirmed = confirm('Are you sure? This will delete all of this debaters stats and affect their team speaks') if (confirmed) { debater.destroy() - .then(() => window.location = debater.pathTo().index) + .then(() => window.location = new Debater().pathTo(false).index) .catch(() => this.setState({ message: 'Could not delete this debater' })) } } diff --git a/client/app/components/debaters/DebaterDetail.js b/client/app/components/debaters/DebaterDetail.js index ccc1ee1..3e8bb75 100644 --- a/client/app/components/debaters/DebaterDetail.js +++ b/client/app/components/debaters/DebaterDetail.js @@ -1,5 +1,5 @@ import React from 'react'; -import School from '../../resources/School' +import {School} from '../../resources/School' export const DebaterDetail = (props) => { const school = new School(props.school.id) diff --git a/client/app/components/debaters/DebaterForm.js b/client/app/components/debaters/DebaterForm.js index 525a9e2..29dd757 100644 --- a/client/app/components/debaters/DebaterForm.js +++ b/client/app/components/debaters/DebaterForm.js @@ -1,6 +1,6 @@ import React from 'react' import {FormControl, ControlLabel, FormGroup, Button} from 'react-bootstrap' -import Debater from '../../resources/Debater' +import {ApiDebater} from '../../resources/Debater' import SchoolSelectField from '../schools/SchoolSelectField' export class DebaterForm extends React.Component { @@ -27,7 +27,7 @@ export class DebaterForm extends React.Component { handleSubmit = (event) => { event.preventDefault() - const debater = new Debater(this.props.id) + const debater = new ApiDebater(this.props.id) let request = this.props.id ? debater.update : debater.create request(this.paramsToSubmit()) .then((response) => this.props.handleSuccessfulSubmit(response)) diff --git a/client/app/components/debaters/DebaterItem.js b/client/app/components/debaters/DebaterItem.js index 8ff155b..fbab152 100644 --- a/client/app/components/debaters/DebaterItem.js +++ b/client/app/components/debaters/DebaterItem.js @@ -1,5 +1,5 @@ import React from 'react' -import Debater from '../../resources/Debater' +import {Debater} from '../../resources/Debater' export const DebaterItem = (props) => { const debater = new Debater(props.id) diff --git a/client/app/components/schools/SchoolDetail.js b/client/app/components/schools/SchoolDetail.js index 0952dd1..9d3be1b 100644 --- a/client/app/components/schools/SchoolDetail.js +++ b/client/app/components/schools/SchoolDetail.js @@ -1,7 +1,7 @@ import React, {PropTypes} from 'react' import {Button} from 'react-bootstrap' import EditableText from '../shared/EditableText' -import School from '../../resources/School' +import {School, ApiSchool} from '../../resources/School' class SchoolDetail extends React.Component { state = { @@ -16,17 +16,16 @@ class SchoolDetail extends React.Component { deleteSchool = (event) => { event.preventDefault() let confirmed = confirm('Are you sure? This will delete all of the debaters and judges') - const school = this.getSchoolObject() if (confirmed) { - school.destroy() - .then((response) => window.location = school.pathTo().index) + new ApiSchool(this.state.school.id).destroy() + .then((response) => window.location = new School.pathTo().index) .catch(() => this.setState({message: 'Could not delete school.'})) } } handleNameUpdate = (name) => { if (name.trim() !== this.state.school.name) { - this.getSchoolObject().update({name}) + new ApiSchool(this.state.school.id).update({name}) .then((response) => { this.flashMessage('School updated!') this.setState({school: response.data}) @@ -40,10 +39,6 @@ class SchoolDetail extends React.Component { setTimeout(() => this.setState({message: ''}), 2500) } - getSchoolObject () { - return new School(this.state.school.id) - } - render () { return (