diff --git a/app/controllers/api/concerns/errors.rb b/app/controllers/api/concerns/errors.rb index 0ac8c130..f08cacb6 100644 --- a/app/controllers/api/concerns/errors.rb +++ b/app/controllers/api/concerns/errors.rb @@ -15,6 +15,8 @@ module Api::Concerns::Errors # more context such as the `title` and `detail`. rescue_from VersionCake::UnsupportedVersionError, with: :unsupported_version rescue_from ActiveRecord::RecordNotFound, with: :not_found + rescue_from ActiveRecord::RecordInvalid, with: :invalid_record + rescue_from ActionController::ParameterMissing, with: :bad_request end private @@ -37,6 +39,20 @@ def not_found(error) api_error ::Api::NotFoundError.new(detail:, backtrace: error.backtrace) end + def invalid_record(error) + api_error ::Api::BadRequestError.new( + detail: error.record&.errors&.full_messages&.join(", "), + backtrace: error.backtrace + ) + end + + def bad_request(error) + api_error ::Api::BadRequestError.new( + detail: error.message, + backtrace: error.backtrace + ) + end + def server_error(error) Rails.logger.error(error.inspect) raise error if Rails.env.development? diff --git a/app/controllers/api/subscriptions_controller.rb b/app/controllers/api/subscriptions_controller.rb new file mode 100644 index 00000000..3cd5a400 --- /dev/null +++ b/app/controllers/api/subscriptions_controller.rb @@ -0,0 +1,13 @@ +class Api::SubscriptionsController < ApiController + def create + ActiveRecord::Base.transaction do + user = User.find_or_create_by!(email_address: params.require(:email)) + @subscription = Hackathon::Subscription.create!( + location_input: params.require(:location), + subscriber: user + ) + end + + render partial: "api/subscriptions/subscription", locals: {subscription: @subscription} + end +end diff --git a/app/controllers/api_controller.rb b/app/controllers/api_controller.rb index 92217052..92d96328 100644 --- a/app/controllers/api_controller.rb +++ b/app/controllers/api_controller.rb @@ -1,4 +1,8 @@ class ApiController < ActionController::Base + # Don't check CSRF token for API routes + skip_before_action :verify_authenticity_token + + # Pagination include Pagy::Backend helper_method :pagy_metadata after_action { pagy_headers_merge(@pagy) if @pagy } diff --git a/app/helpers/api_helper.rb b/app/helpers/api_helper.rb index ff9e821b..befed65e 100644 --- a/app/helpers/api_helper.rb +++ b/app/helpers/api_helper.rb @@ -36,7 +36,9 @@ def obj(json, object) json.created_at object.created_at if object.respond_to?(:created_at) json.links do - json.self api_url_for(object) + api_url_for(object).tap do |url| + json.self url if url + end end end @@ -44,6 +46,8 @@ def obj(json, object) # the same as the current request's version. def api_url_for(object, **options) polymorphic_url([:api, object], api_version: @request_version, **options) + rescue NoMethodError + nil end # Permanent URL for an attached file or variant diff --git a/app/views/api/subscriptions/_subscription.json.jbuilder b/app/views/api/subscriptions/_subscription.json.jbuilder new file mode 100644 index 00000000..61c7f6da --- /dev/null +++ b/app/views/api/subscriptions/_subscription.json.jbuilder @@ -0,0 +1,11 @@ +obj json, subscription do + json.status subscription.status + + json.location do + json.extract! subscription, :city, :province, :country_code, :postal_code + end + + json.subscriber do + json.partial! "api/users/user", user: subscription.subscriber + end +end diff --git a/app/views/api/users/_user.json.jbuilder b/app/views/api/users/_user.json.jbuilder new file mode 100644 index 00000000..48e6b63f --- /dev/null +++ b/app/views/api/users/_user.json.jbuilder @@ -0,0 +1,3 @@ +obj json, user do + json.extract! user, :email_address +end diff --git a/config/routes.rb b/config/routes.rb index cb2cb71f..422ef181 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -10,6 +10,7 @@ namespace :api, defaults: {format: :json} do scope "/v:api_version" do resources :hackathons, only: [:index, :show] + resources :subscriptions, only: :create end end diff --git a/test/controllers/api/subscriptions_controller_test.rb b/test/controllers/api/subscriptions_controller_test.rb new file mode 100644 index 00000000..a04c30d9 --- /dev/null +++ b/test/controllers/api/subscriptions_controller_test.rb @@ -0,0 +1,54 @@ +require "test_helper" + +class Api::SubscriptionsControllerTest < ActionDispatch::IntegrationTest + test "should create" do + assert_difference("Hackathon::Subscription.count", 1) do + post api_subscriptions_url api_version: 1, + params: { + email: "gary@hackclub.com", + location: "Seattle, WA" + }, + as: :json + end + + subscription = @response.parsed_body + + assert_equal subscription["id"], Hackathon::Subscription.last.hashid + end + + test "should not create with invalid email" do + assert_no_difference("Hackathon::Subscription.count") do + post api_subscriptions_url api_version: 1, + params: { + email: "gary", + location: "Seattle, WA" + }, + as: :json + end + + assert_response :bad_request + + error = @response.parsed_body + assert_equal error["type"], "bad_request_error" + end + + test "should not create with missing email" do + assert_no_difference("Hackathon::Subscription.count") do + post api_subscriptions_url api_version: 1, + params: { + location: "Seattle, WA" + }, + as: :json + end + end + + test "should not create with missing location" do + assert_no_difference("Hackathon::Subscription.count") do + post api_subscriptions_url api_version: 1, + params: { + email: "gary@hackclub.com" + }, + as: :json + end + end +end