app/controllers/plans_controller.rb
class PlansController < ApplicationController
skip_before_action :check_http_auth,
:verify_authenticity_token,
:fetch_logged_in_user,
:fetch_community,
:perform_redirect,
:fetch_community_membership
before_action :ensure_external_plan_service_in_use!
# Request data types
NewPlanRequest = EntityUtils.define_builder(
[:marketplace_id, :fixnum, :mandatory],
[:features, :hash, :mandatory],
[:status, :to_symbol, one_of: [:trial, :active, :hold]],
[:expires_at, :utc_str_to_time, :optional],
)
NewPlansRequest = EntityUtils.define_builder(
[:plans, :mandatory, collection: NewPlanRequest]
)
# Response data types
NewPlanResponse = EntityUtils.define_builder(
[:marketplace_plan_id, :fixnum, :mandatory],
[:marketplace_id, :fixnum, :mandatory],
[:features, :hash, :mandatory],
[:expires_at, :time, :optional],
[:created_at, :time, :mandatory],
[:updated_at, :time, :mandatory],
)
NewPlansResponse = EntityUtils.define_builder(
[:plans, :mandatory, collection: NewPlanResponse]
)
# Map from External service key names to Entity key names
EXT_SERVICE_TO_ENTITY_MAP = {
:marketplace_id => :community_id,
:marketplace_plan_id => :id
}
ENTITY_TO_EXT_SERVICE_MAP = EXT_SERVICE_TO_ENTITY_MAP.invert
MAX_TRIALS_LIMIT = 1000
def create
parse_token(params).and_then {
plans_api.authorize_provisioning(params[:token])
}.and_then {
body = request.raw_post
logger.info("Received plan notification", nil, {raw: body})
parse_json(request.raw_post)
}.and_then { |parsed_json|
NewPlansRequest.validate(parsed_json)
}.and_then { |parsed_request|
logger.info("Parsed plan notification", nil, parsed_request)
create_plan_operations = parsed_request[:plans].map { |plan_request|
plan_entity = to_entity(plan_request)
->(*) {
PlanService::API::Api.plans.create(community_id: plan_entity[:community_id], plan: plan_entity)
}
}
if create_plan_operations.present?
Result.all(*create_plan_operations)
else
# Nothing to save
Result::Success.new([])
end
}.on_success { |created_plans|
created_plans&.each do |plan|
if plan.dig(:features, :landing_page) == false
LandingPage.where(community_id: plan[:community_id]).update_all(enabled: false)
end
if plan.dig(:features, :whitelabel) == false
Community.find_by(id: plan[:community_id])&.update(use_domain: false)
end
end
logger.info("Created new plans based on the notification", nil, created_plans)
response = NewPlansResponse.build(plans: created_plans.map { |plan_entity| from_entity(plan_entity) })
render json: response, status: :ok
}.on_error { |error_msg, data|
case data
when JSON::ParserError
logger.error("Error while parsing JSON: #{data.message}")
render json: {error: :json_parser_error}, status: :bad_request
when :verification_error,
:expired_signature,
:invalid_sub_error,
:token_missing
logger.error("Unauthorized", nil, error: data, token: params[:token])
render json: {error: :unauthorized}, status: :unauthorized
else
logger.error("Unknown error")
render json: {error: :unknown_error}, status: :internal_server_error
end
}
end
# Returns paginated trials
#
# Result:
#
# ```
# { plans: [ ... ],
# next_after: 1234567890
# }
def get_trials
parse_token(params).and_then {
plans_api.authorize_trial_sync(params[:token])
}.and_then {
after = Maybe(params)[:after].to_i.map { |time_int| Time.at(time_int).utc }.or_else(nil)
limit = Maybe(params)[:limit].to_i.or_else(MAX_TRIALS_LIMIT)
logger.info("Fetching plans that are created after #{after}", nil, {after: after, limit: limit})
PlanService::API::Api.plans.get_trials(after: after, limit: limit)
}.on_success { |res|
return_value = res.merge(plans: res[:plans].map { |p| from_entity(p) })
count = return_value[:plans].count
render json: return_value
logger.info("Returned #{count} plans", nil, {count: count})
}.on_error { |error_msg, data|
case data
when :verification_error,
:expired_signature,
:invalid_sub_error,
:token_missing
logger.error("Unauthorized", nil, error: data, token: params[:token])
render json: {error: :unauthorized}, status: :unauthorized
else
logger.error("Unknown error")
render json: {error: :unknown_error}, status: :internal_server_error
end
}
end
private
def logger
PlanService::API::Api.logger
end
def parse_json(body)
begin
Result::Success.new(JSONUtils.symbolize_keys(JSON.parse(body)))
rescue StandardError => e
Result::Error.new(e)
end
end
def parse_token(params)
if params[:token]
Result::Success.new(params[:token])
else
Result::Error.new("JWT Token missing", :token_missing)
end
end
def from_entity(entity)
HashUtils.rename_keys(ENTITY_TO_EXT_SERVICE_MAP, entity)
end
def to_entity(hash)
HashUtils.rename_keys(EXT_SERVICE_TO_ENTITY_MAP, hash)
end
# filters
def ensure_external_plan_service_in_use!
render_not_found! unless plans_api.active?
end
def plans_api
PlanService::API::Api.plans
end
end