ソースを参照

Begin work on a Cool And New JSON API

- Add API-controller, permitting RO access to some resources when logged
    in:
    - Activities
    - Groups
    - Own Person
- Use rabl for json templating
Maarten van den Berg 7 年 前
コミット
437673cffd

+ 4 - 0
Gemfile

39
 # Use HAML for templates
39
 # Use HAML for templates
40
 gem 'haml'
40
 gem 'haml'
41
 
41
 
42
+# Use RABL for JSON
43
+gem 'rabl'
44
+gem 'oj'
45
+
42
 # Use Fontawesome icons
46
 # Use Fontawesome icons
43
 gem 'font-awesome-sass'
47
 gem 'font-awesome-sass'
44
 
48
 

+ 5 - 0
Gemfile.lock

110
     nio4r (2.0.0)
110
     nio4r (2.0.0)
111
     nokogiri (1.7.1)
111
     nokogiri (1.7.1)
112
       mini_portile2 (~> 2.1.0)
112
       mini_portile2 (~> 2.1.0)
113
+    oj (3.3.5)
113
     pg (0.20.0)
114
     pg (0.20.0)
114
     puma (3.8.2)
115
     puma (3.8.2)
116
+    rabl (0.13.1)
117
+      activesupport (>= 2.3.14)
115
     rack (2.0.1)
118
     rack (2.0.1)
116
     rack-test (0.6.3)
119
     rack-test (0.6.3)
117
       rack (>= 1.0)
120
       rack (>= 1.0)
211
   jquery-rails
214
   jquery-rails
212
   listen (~> 3.0.5)
215
   listen (~> 3.0.5)
213
   mailgun_rails
216
   mailgun_rails
217
+  oj
214
   pg
218
   pg
215
   puma (~> 3.0)
219
   puma (~> 3.0)
220
+  rabl
216
   rails (~> 5.0.0, >= 5.0.0.1)
221
   rails (~> 5.0.0, >= 5.0.0.1)
217
   sass-rails (~> 5.0)
222
   sass-rails (~> 5.0)
218
   spring
223
   spring

+ 0 - 3
app/assets/javascripts/authentication.coffee

1
-# Place all the behaviors and hooks related to the matching controller here.
2
-# All this logic will automatically be available in application.js.
3
-# You can use CoffeeScript in this file: http://coffeescript.org/

+ 3 - 0
app/assets/stylesheets/api.scss

1
+// Place all the styles related to the Api controller here.
2
+// They will automatically be included in application.css.
3
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 3 - 0
app/assets/stylesheets/api/me.scss

1
+// Place all the styles related to the Api::Me controller here.
2
+// They will automatically be included in application.css.
3
+// You can use Sass (SCSS) here: http://sass-lang.com/

+ 23 - 0
app/controllers/api/activities_controller.rb

1
+class Api::ActivitiesController < ApiController
2
+  before_action :set_activity, only: [:show]
3
+  before_action :require_membership!, only: [:show]
4
+  before_action :api_require_admin!, only: [:index]
5
+
6
+  # GET /api/activities
7
+  # GET /api/activities.json
8
+  def index
9
+    @activities = Activity.all
10
+  end
11
+
12
+  # GET /api/activities/1
13
+  # GET /api/activities/1.json
14
+  def show
15
+  end
16
+
17
+  private
18
+    # Use callbacks to share common setup or constraints between actions.
19
+    def set_activity
20
+      @activity = Activity.find(params[:id])
21
+      @group = @activity.group
22
+    end
23
+end

+ 21 - 0
app/controllers/api/groups_controller.rb

1
+class Api::GroupsController < ApiController
2
+  before_action :set_group, only: [:show]
3
+  before_action :require_membership!, only: [:show]
4
+  before_action :api_require_admin!, only: [:index]
5
+
6
+  # GET /api/groups
7
+  # GET /api/groups.json
8
+  def index
9
+    @api_groups = Api::Group.all
10
+  end
11
+
12
+  # GET /api/groups/1
13
+  def show
14
+  end
15
+
16
+  private
17
+    # Use callbacks to share common setup or constraints between actions.
18
+    def set_group
19
+      @group = Group.find(params[:id])
20
+    end
21
+end

+ 11 - 0
app/controllers/api/me_controller.rb

1
+class Api::MeController < ApiController
2
+  def index
3
+    @person = current_person
4
+    render 'api/people/show'
5
+  end
6
+
7
+  def groups
8
+    @groups = current_person.groups
9
+    render 'api/groups/index'
10
+  end
11
+end

+ 21 - 0
app/controllers/api/people_controller.rb

1
+class Api::PeopleController < ApiController
2
+  before_action :set_person, only: [:show]
3
+  before_action :api_require_admin! # Normal people should use /me
4
+
5
+  # GET /api/people
6
+  # GET /api/people.json
7
+  def index
8
+    @people = Person.all
9
+  end
10
+
11
+  # GET /api/people/1
12
+  # GET /api/people/1.json
13
+  def show
14
+  end
15
+
16
+  private
17
+    # Use callbacks to share common setup or constraints between actions.
18
+    def set_person
19
+      @person = Person.find(params[:id])
20
+    end
21
+end

+ 32 - 0
app/controllers/api_controller.rb

1
+class ApiController < ActionController::Base
2
+  include AuthenticationHelper
3
+
4
+  before_action :api_require_authentication!, except: [:status]
5
+
6
+  def status
7
+    @message = "Ok"
8
+    render 'api/ok'
9
+  end
10
+
11
+  protected
12
+  def api_require_authentication!
13
+    if !is_logged_in?
14
+      head :unauthorized
15
+    end
16
+  end
17
+
18
+  def api_require_admin!
19
+    if !current_person.is_admin?
20
+      @message = I18n.t('authentication.admin_required')
21
+      render 'api/error', status: :forbidden
22
+    end
23
+  end
24
+
25
+  # Require user to be a member of group OR admin, requires @group set
26
+  def require_membership!
27
+    if !current_person.groups.include?(@group) && !current_person.is_admin?
28
+      @message = I18n.t('authentication.membership_required')
29
+      render 'api/error', status: :forbidden
30
+    end
31
+  end
32
+end

+ 2 - 0
app/helpers/api/activities_helper.rb

1
+module Api::ActivitiesHelper
2
+end

+ 2 - 0
app/helpers/api/groups_helper.rb

1
+module Api::GroupsHelper
2
+end

+ 2 - 0
app/helpers/api/me_helper.rb

1
+module Api::MeHelper
2
+end

+ 2 - 0
app/helpers/api/people_helper.rb

1
+module Api::PeopleHelper
2
+end

+ 2 - 0
app/helpers/api_helper.rb

1
+module ApiHelper
2
+end

+ 3 - 0
app/views/api/activities/index.rabl

1
+collection @activities
2
+
3
+attributes :id, :name

+ 23 - 0
app/views/api/activities/show.rabl

1
+object @activity
2
+
3
+attributes :id, :name, :start, :end, :deadline, :location
4
+
5
+node :response_counts do
6
+  c = @activity.state_counts
7
+  {
8
+    "present": c[true]  || "0",
9
+    "unknown": c[nil]   || "0",
10
+    "absent":  c[false] || "0"
11
+  }
12
+end
13
+
14
+child :participants do
15
+  child :person do
16
+    attribute :id, :full_name
17
+  end
18
+  attribute :attending, :notes, :is_organizer
19
+end
20
+
21
+child :group do
22
+  attribute :id, :name
23
+end

+ 9 - 0
app/views/api/error.rabl

1
+object false
2
+
3
+node :ok do
4
+  false
5
+end
6
+
7
+node :message do
8
+  @message
9
+end

+ 3 - 0
app/views/api/groups/index.rabl

1
+collection @groups
2
+
3
+attribute :id, :name

+ 14 - 0
app/views/api/groups/show.rabl

1
+object @group
2
+
3
+attributes :id, :name
4
+
5
+child :members do
6
+  child :person do
7
+    attribute :id, :full_name
8
+  end
9
+  attribute :is_leader
10
+end
11
+
12
+child :activities do
13
+  attributes :id, :name
14
+end

+ 9 - 0
app/views/api/ok.rabl

1
+object false
2
+
3
+node :ok do
4
+  true
5
+end
6
+
7
+node :message do
8
+  @message
9
+end

+ 3 - 0
app/views/api/people/show.rabl

1
+object @person
2
+
3
+attributes :first_name, :infix, :last_name, :full_name

+ 1 - 0
config/locales/authentication/en.yml

20
     login_required: "You need to be logged in to do that."
20
     login_required: "You need to be logged in to do that."
21
     admin_required: "You need to be an administrator to do that."
21
     admin_required: "You need to be an administrator to do that."
22
     organizer_required: "You need to be an organizer to do that."
22
     organizer_required: "You need to be an organizer to do that."
23
+    membership_required: "You need to be a member of that group to do that."
23
     activation_required: "Your account has not yet been activated, please confirm your account using the email you received."
24
     activation_required: "Your account has not yet been activated, please confirm your account using the email you received."
24
     already_activated: "Your account has already been activated, please use the 'Forgot password' form if you want to reset your password."
25
     already_activated: "Your account has already been activated, please use the 'Forgot password' form if you want to reset your password."
25
     password_repeat_mismatch: "Password confirmation does not match your password!"
26
     password_repeat_mismatch: "Password confirmation does not match your password!"

+ 1 - 0
config/locales/authentication/nl.yml

20
     login_required: "Je moet eerst inloggen."
20
     login_required: "Je moet eerst inloggen."
21
     admin_required: "Je moet een beheerder zijn om dat te doen."
21
     admin_required: "Je moet een beheerder zijn om dat te doen."
22
     organizer_required: "Je moet een organisator zijn om dat te doen."
22
     organizer_required: "Je moet een organisator zijn om dat te doen."
23
+    membership_required: "Je moet lid zijn van die groep om dat te doen."
23
     activation_required: "Je account is nog niet geactiveerd! Bevestig je e-mailadres door op de link te klikken die je in je mail hebt gehad."
24
     activation_required: "Je account is nog niet geactiveerd! Bevestig je e-mailadres door op de link te klikken die je in je mail hebt gehad."
24
     already_activated: "Je account is al geactiveerd! Gebruik het 'Wachtwoord vergeten'-formulier als je je wachtwoord opnieuw in wilt stellen."
25
     already_activated: "Je account is al geactiveerd! Gebruik het 'Wachtwoord vergeten'-formulier als je je wachtwoord opnieuw in wilt stellen."
25
     password_repeat_mismatch: "Je hebt niet twee keer hetzelfde wachtwoord ingevuld!"
26
     password_repeat_mismatch: "Je hebt niet twee keer hetzelfde wachtwoord ingevuld!"

+ 5 - 2
config/puma.rb

13
 
13
 
14
 # Specifies the `environment` that Puma will run in.
14
 # Specifies the `environment` that Puma will run in.
15
 #
15
 #
16
-environment ENV.fetch("RAILS_ENV") { "development" }
16
+env = ENV.fetch("RAILS_ENV") { "development" }
17
+environment env
17
 
18
 
18
 state_path "#{ENV['AARDBEI_PATH']}/tmp/pids/puma.state"
19
 state_path "#{ENV['AARDBEI_PATH']}/tmp/pids/puma.state"
19
-stdout_redirect "#{ENV['AARDBEI_PATH']}/log/stdout", "#{ENV['AARDBEI_PATH']}/log/stderr", true
20
+if env == "production"
21
+  stdout_redirect "#{ENV['AARDBEI_PATH']}/log/stdout", "#{ENV['AARDBEI_PATH']}/log/stderr", true
22
+end
20
 
23
 
21
 
24
 
22
 # Specifies the number of `workers` to boot in clustered mode.
25
 # Specifies the number of `workers` to boot in clustered mode.

+ 11 - 0
config/routes.rb

49
   get 'my_groups', to: 'groups#user_groups', as: :user_groups
49
   get 'my_groups', to: 'groups#user_groups', as: :user_groups
50
 
50
 
51
   # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
51
   # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
52
+  namespace 'api' do
53
+    get 'status'
54
+    scope 'me' do
55
+      root to: 'me#index'
56
+      get 'groups', to: 'me#groups'
57
+    end
58
+
59
+    resources :groups, only: [:index, :show]
60
+    resources :activities, only: [:index, :show]
61
+    resources :people, only: [:index, :show]
62
+  end
52
 end
63
 end

+ 48 - 0
test/controllers/api/activities_controller_test.rb

1
+require 'test_helper'
2
+
3
+class Api::ActivitiesControllerTest < ActionDispatch::IntegrationTest
4
+  setup do
5
+    @api_activity = api_activities(:one)
6
+  end
7
+
8
+  test "should get index" do
9
+    get api_activities_url
10
+    assert_response :success
11
+  end
12
+
13
+  test "should get new" do
14
+    get new_api_activity_url
15
+    assert_response :success
16
+  end
17
+
18
+  test "should create api_activity" do
19
+    assert_difference('Api::Activity.count') do
20
+      post api_activities_url, params: { api_activity: {  } }
21
+    end
22
+
23
+    assert_redirected_to api_activity_url(Api::Activity.last)
24
+  end
25
+
26
+  test "should show api_activity" do
27
+    get api_activity_url(@api_activity)
28
+    assert_response :success
29
+  end
30
+
31
+  test "should get edit" do
32
+    get edit_api_activity_url(@api_activity)
33
+    assert_response :success
34
+  end
35
+
36
+  test "should update api_activity" do
37
+    patch api_activity_url(@api_activity), params: { api_activity: {  } }
38
+    assert_redirected_to api_activity_url(@api_activity)
39
+  end
40
+
41
+  test "should destroy api_activity" do
42
+    assert_difference('Api::Activity.count', -1) do
43
+      delete api_activity_url(@api_activity)
44
+    end
45
+
46
+    assert_redirected_to api_activities_url
47
+  end
48
+end

+ 48 - 0
test/controllers/api/groups_controller_test.rb

1
+require 'test_helper'
2
+
3
+class Api::GroupsControllerTest < ActionDispatch::IntegrationTest
4
+  setup do
5
+    @api_group = api_groups(:one)
6
+  end
7
+
8
+  test "should get index" do
9
+    get api_groups_url
10
+    assert_response :success
11
+  end
12
+
13
+  test "should get new" do
14
+    get new_api_group_url
15
+    assert_response :success
16
+  end
17
+
18
+  test "should create api_group" do
19
+    assert_difference('Api::Group.count') do
20
+      post api_groups_url, params: { api_group: {  } }
21
+    end
22
+
23
+    assert_redirected_to api_group_url(Api::Group.last)
24
+  end
25
+
26
+  test "should show api_group" do
27
+    get api_group_url(@api_group)
28
+    assert_response :success
29
+  end
30
+
31
+  test "should get edit" do
32
+    get edit_api_group_url(@api_group)
33
+    assert_response :success
34
+  end
35
+
36
+  test "should update api_group" do
37
+    patch api_group_url(@api_group), params: { api_group: {  } }
38
+    assert_redirected_to api_group_url(@api_group)
39
+  end
40
+
41
+  test "should destroy api_group" do
42
+    assert_difference('Api::Group.count', -1) do
43
+      delete api_group_url(@api_group)
44
+    end
45
+
46
+    assert_redirected_to api_groups_url
47
+  end
48
+end

+ 7 - 0
test/controllers/api/me_controller_test.rb

1
+require 'test_helper'
2
+
3
+class Api::MeControllerTest < ActionDispatch::IntegrationTest
4
+  # test "the truth" do
5
+  #   assert true
6
+  # end
7
+end

+ 48 - 0
test/controllers/api/people_controller_test.rb

1
+require 'test_helper'
2
+
3
+class Api::PeopleControllerTest < ActionDispatch::IntegrationTest
4
+  setup do
5
+    @api_person = api_people(:one)
6
+  end
7
+
8
+  test "should get index" do
9
+    get api_people_url
10
+    assert_response :success
11
+  end
12
+
13
+  test "should get new" do
14
+    get new_api_person_url
15
+    assert_response :success
16
+  end
17
+
18
+  test "should create api_person" do
19
+    assert_difference('Api::Person.count') do
20
+      post api_people_url, params: { api_person: {  } }
21
+    end
22
+
23
+    assert_redirected_to api_person_url(Api::Person.last)
24
+  end
25
+
26
+  test "should show api_person" do
27
+    get api_person_url(@api_person)
28
+    assert_response :success
29
+  end
30
+
31
+  test "should get edit" do
32
+    get edit_api_person_url(@api_person)
33
+    assert_response :success
34
+  end
35
+
36
+  test "should update api_person" do
37
+    patch api_person_url(@api_person), params: { api_person: {  } }
38
+    assert_redirected_to api_person_url(@api_person)
39
+  end
40
+
41
+  test "should destroy api_person" do
42
+    assert_difference('Api::Person.count', -1) do
43
+      delete api_person_url(@api_person)
44
+    end
45
+
46
+    assert_redirected_to api_people_url
47
+  end
48
+end

+ 7 - 0
test/controllers/api_controller_test.rb

1
+require 'test_helper'
2
+
3
+class ApiControllerTest < ActionDispatch::IntegrationTest
4
+  # test "the truth" do
5
+  #   assert true
6
+  # end
7
+end