Browse Source

feat: backend code for player calendar

Refs: #30
pull/51/head
Martin Bober 2 weeks ago
parent
commit
bb19997c4b
  1. 58
      app/controllers/api_player_controller.rb
  2. 47
      app/models/player.rb
  3. 3
      app/views/shared/_api_player_own.json.jbuilder
  4. 3
      config/routes.rb
  5. 5
      db/migrate/20221113095016_add_calendar_token_to_player.rb
  6. 3
      db/schema.rb
  7. 8
      public/openapi.yaml
  8. 82
      test/controllers/api_player_controller_test.rb
  9. 1
      test/fixtures/players.yml

58
app/controllers/api_player_controller.rb

@ -2,8 +2,8 @@ class ApiPlayerController < ApplicationController
include ApiHelper
include PlayersHelper
before_action :check_mime
before_action :get_player, only: [:show, :update, :validate]
before_action :check_mime, except: [:get_player_calendar]
before_action :get_player, only: [:show, :update, :validate, :get_player_calendar, :create_player_calendar_token, :delete_player_calendar_token]
protect_from_forgery with: :null_session
@ -132,6 +132,60 @@ class ApiPlayerController < ApplicationController
}
end
def get_player_calendar
unless params.key? :token
render status: :bad_request, json: {
error: "calendar_token_not_provided"
}
return
end
unless @player.calendar_token == params[:token]
render status: :forbidden, json: {
error: "invalid_calendar_token"
}
return
end
send_data @player.calendar.to_ical, type: 'text/calendar', disposition: 'attachment', filename: "[#{@player.name}].ics"
end
def create_player_calendar_token
unless logged_in?
render status: :unauthorized, json: {
error: "invalid_token"
}
return
end
unless current_player?(@player)
render status: :forbidden, json: {
error: "forbidden",
error_description: "You are not the player"
}
return
end
@player.calendar_token = Player.generate_calendar_token
@player.save
render partial: "shared/api_player_own", locals: {player: @player}, status: :created
end
def delete_player_calendar_token
unless logged_in?
render status: :unauthorized, json: {
error: "invalid_token"
}
return
end
unless current_player?(@player)
render status: :forbidden, json: {
error: "forbidden",
error_description: "You are not the player"
}
return
end
@player.calendar_token = nil
@player.save
render partial: "shared/api_player_own", locals: {player: @player}
end
private
def update_player

47
app/models/player.rb

@ -112,4 +112,51 @@ class Player < ApplicationRecord
end
end
def Player.generate_calendar_token
token = SecureRandom.urlsafe_base64.downcase
while Player.where(calendar_token: token).any?
token = SecureRandom.urlsafe_base64.downcase
end
token
end
def calendar
cal = Icalendar::Calendar.new
# noinspection RubyResolve
cal.prodid = '-//Martin Bober//NONSGML charXchange//EN'
cal.version = '2.0'
tzid = 'UTC'
session_participants.where(updated_at: 1.year.ago..Time.current).each do |participant|
campaign_session = participant.campaign_session
cal.event do |e|
e.dtstart = Icalendar::Values::DateTime.new(campaign_session.start, tzid: tzid)
e.dtend = Icalendar::Values::DateTime.new(campaign_session.end, tzid: tzid)
e.summary = "[#{campaign_session.campaign.name}] #{campaign_session.name}"
e.description = I18n.t('campaign_session.show.ical_description', session: campaign_session.name, campaign: campaign_session.campaign.name, description: campaign_session.description, url: Rails.application.routes.url_helpers.campaign_forum_url(campaign_session.campaign, campaign_session.forum_thread))
e.organizer = 'MAILTO:no-reply@penpaperbox.com'
e.url = Rails.application.routes.url_helpers.campaign_forum_url campaign_session.campaign, campaign_session.forum_thread
e.uid = Rails.application.routes.url_helpers.campaign_session_url campaign_session.campaign, campaign_session
e.sequence = DateTime.now.strftime('%y%W%u%H%M').to_i
if campaign_session.cancelled or participant.status == :not_attending
e.status = 'CANCELLED'
elsif participant.status == :attending
e.status = 'CONFIRMED'
else
e.status = 'TENTATIVE'
end
# noinspection RubyResolve
e.alarm do |a|
a.action = 'DISPLAY' # This line isn't necessary, it's the default
a.summary = "[#{campaign_session.campaign.name}] #{campaign_session.name}"
a.trigger = '-PT15M0S' # 15 minutes before
end
end
end
cal.publish
cal
end
end

3
app/views/shared/_api_player_own.json.jbuilder

@ -1,2 +1,3 @@
json.partial! 'shared/api_player_details', player: player
json.extract! player, :show_gender, :theme, :locale
json.extract! player, :show_gender, :theme, :locale
json.calendar_token player.calendar_token if player.calendar_token.present?

3
config/routes.rb

@ -158,6 +158,9 @@ Rails.application.routes.draw do
post 'players/me/resend_validation', controller: :api_player, action: :resend_validation
resources :players, controller: :api_player, only: [:index, :show, :update, :create] do
post 'validate', controller: :api_player, action: :validate
get 'calendar', controller: :api_player, action: :get_player_calendar
post 'calendar', controller: :api_player, action: :create_player_calendar_token
delete 'calendar', controller: :api_player, action: :delete_player_calendar_token
end
resources :characters, controller: :api_character, only: [:index, :show, :update, :create, :destroy] do
get 'history', controller: :api_character, action: :get_history

5
db/migrate/20221113095016_add_calendar_token_to_player.rb

@ -0,0 +1,5 @@
class AddCalendarTokenToPlayer < ActiveRecord::Migration[5.2]
def change
add_column :players, :calendar_token, :string, index: true
end
end

3
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: 2022_05_15_100551) do
ActiveRecord::Schema.define(version: 2022_11_13_095016) do
create_table "access_tokens", options: "ENGINE=InnoDB DEFAULT CHARSET=latin1", force: :cascade do |t|
t.string "token"
@ -373,6 +373,7 @@ ActiveRecord::Schema.define(version: 2022_05_15_100551) do
t.boolean "show_gender", default: false
t.string "theme"
t.boolean "email_validated", default: false
t.string "calendar_token"
t.index ["email"], name: "index_players_on_email", unique: true
end

8
public/openapi.yaml

@ -675,13 +675,13 @@ paths:
'application/json':
schema:
$ref: '#/components/schemas/Error'
/1.0/payers/{id}/calendar:
/1.0/players/{player_id}/calendar:
get:
operationId: GetPlayerCalendar
tags:
- "Players"
parameters:
- name: id
- name: player_id
in: path
required: true
schema:
@ -712,7 +712,7 @@ paths:
- "Players"
description: Create a new calendar access token for the player. If one already exists, it becomes invalid.
parameters:
- name: id
- name: player_id
in: path
required: true
description: ID of the player
@ -743,7 +743,7 @@ paths:
- "Players"
description: Invalidates this player's calendar token
parameters:
- name: id
- name: player_id
in: path
required: true
description: ID of the player

82
test/controllers/api_player_controller_test.rb

@ -379,4 +379,86 @@ class ApiPlayerControllerTest < ActionController::TestCase
assert_not player.experienced_systems.where(name: rpg_system.name).any?
end
test 'anon cannot get player calendar without token' do
get :get_player_calendar, params: {
player_id: @player.id
}
assert_response :bad_request
end
test 'anon cannot get player calendar with incorrect token' do
get :get_player_calendar, params: {
player_id: @player.id,
token: @player.calendar_token + 'a'
}
assert_response :forbidden
end
test 'anon can get player calendar with correct token' do
get :get_player_calendar, params: {
player_id: @player.id,
token: @player.calendar_token
}
assert_response :success
end
test 'anon cannot create player calendar token' do
current_token = @player.calendar_token
post :create_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :unauthorized
@player.reload
assert_equal current_token, @player.calendar_token
end
test 'other player cannot create player calendar token' do
log_in_as @other
current_token = @player.calendar_token
post :create_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :forbidden
@player.reload
assert_equal current_token, @player.calendar_token
end
test 'player can create player calendar token for himself' do
log_in_as @player
current_token = @player.calendar_token
post :create_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :created
@player.reload
assert_not_equal current_token, @player.calendar_token
end
test 'anon cannot delete player calendar token' do
delete :delete_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :unauthorized
assert @player.calendar_token.present?
end
test 'other player cannot delete player calendar token' do
log_in_as @other
delete :delete_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :forbidden
assert @player.calendar_token.present?
end
test 'player can delete player calendar token himself' do
log_in_as @player
delete :delete_player_calendar_token, as: :json, params: {
player_id: @player.id
}
assert_response :success
@player.reload
assert_not @player.calendar_token.present?
end
end

1
test/fixtures/players.yml vendored

@ -17,6 +17,7 @@ bob:
- two
languages:
- one
calendar_token: abc
joe:
name: Joe

Loading…
Cancel
Save