diff --git a/app/controllers/api/v1/addresses_controller.rb b/app/controllers/api/v1/addresses_controller.rb new file mode 100644 index 00000000..c972f4f6 --- /dev/null +++ b/app/controllers/api/v1/addresses_controller.rb @@ -0,0 +1,41 @@ +class Api::V1::AddressesController < ApplicationController + before_action :set_addressable + + def create + @address = @addressable.addresses.build(address_params) + if @address.save + render json: @address, status: :created + else + render json: @address.errors, status: :unprocessable_entity + end + end + + def update + @address = @addressable.addresses.find(params[:id]) + if @address.update(address_params) + render json: @address, status: :ok + else + render json: @address.errors, status: :unprocessable_entity + end + end + + def destroy + @address = @addressable.addresses.find(params[:id]) + @address.destroy + head :no_content + end + + private + + def set_addressable + @addressable = if params[:user_id] + User.find(params[:user_id]) + elsif params[:organization_id] + Organization.find(params[:organization_id]) + end + end + + def address_params + params.require(:address).permit(:street_address, :city, :state, :postal_code, :save_to_user) + end +end diff --git a/app/controllers/api/v1/orders_controller.rb b/app/controllers/api/v1/orders_controller.rb index 842add1e..5cf5ca51 100644 --- a/app/controllers/api/v1/orders_controller.rb +++ b/app/controllers/api/v1/orders_controller.rb @@ -8,9 +8,10 @@ def index # POST /api/v1/orders def create - @order.user = current_user # Automatically associate user - + @order = Order.new(order_params) + @order.user = current_user if @order.save + associate_address_with_user(@order) render json: @order, status: :created else render json: { errors: @order.errors.full_messages }, status: :unprocessable_entity @@ -25,6 +26,7 @@ def show # PATCH/PUT /api/v1/orders/:id def update if @order.update(order_params) + associate_address_with_user(@order) render json: @order, status: :ok else render json: { errors: @order.errors.full_messages }, status: :unprocessable_entity @@ -40,7 +42,22 @@ def destroy private + def set_order + @order = Order.find(params[:id]) + end + def order_params params.require(:order).permit(:phone, :address_id, :school_year, :comments, :product_id, :product_type) end + + def associate_address_with_user(order) + if order.address && order.user + # Add a condition to check if the address should be saved to user + if order.address.save_to_user + unless order.user.addresses.exists?(order.address.id) + order.user.addresses << order.address + end + end + end + end end diff --git a/app/models/address.rb b/app/models/address.rb index 49940172..034d3585 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -2,6 +2,8 @@ class Address < ApplicationRecord belongs_to :addressable, polymorphic: true has_many :orders + attribute :save_to_user, :boolean, default: false + validates :street_address, :city, :state, :postal_code, :addressable, presence: true validates :postal_code, format: { with: /\A\d{5}(-\d{4})?\z/, message: "must be a valid postal code" } end diff --git a/app/models/order.rb b/app/models/order.rb index e37515f7..6c60154b 100644 --- a/app/models/order.rb +++ b/app/models/order.rb @@ -3,6 +3,7 @@ class Order < ApplicationRecord belongs_to :product, polymorphic: true belongs_to :address + accepts_nested_attributes_for :address, allow_destroy: true before_validation :normalize_phone_number # Validates that phone number is in the right 10 digit format diff --git a/config/routes.rb b/config/routes.rb index 2da6bd96..84093104 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -23,6 +23,7 @@ post "kit_items_only", to: "kit_items#create_kit_items_only" patch "kit_items_only/:id", to: "kit_items#update_kit_items_only" resources :users do + resources :addresses, only: [ :create, :update, :destroy ] member do get "profile" end diff --git a/db/migrate/20250110130617_add_save_to_user_to_addresses.rb b/db/migrate/20250110130617_add_save_to_user_to_addresses.rb new file mode 100644 index 00000000..8a85266c --- /dev/null +++ b/db/migrate/20250110130617_add_save_to_user_to_addresses.rb @@ -0,0 +1,5 @@ +class AddSaveToUserToAddresses < ActiveRecord::Migration[7.2] + def change + add_column :addresses, :save_to_user, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index bd644aab..7d32e426 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -48,6 +48,7 @@ t.datetime "updated_at", null: false t.string "addressable_type", null: false t.integer "addressable_id", null: false + t.boolean "save_to_user" t.index ["addressable_type", "addressable_id"], name: "index_addresses_on_addressable" end @@ -94,6 +95,8 @@ t.datetime "updated_at", null: false t.string "payment_token" t.boolean "canceled", default: false + t.string "stripe_checkout_session_id" + t.string "stripe_payment_intent_id" t.index ["user_id"], name: "index_donations_on_user_id" end @@ -134,9 +137,9 @@ t.string "phone" t.text "comments" t.integer "user_id" + t.string "product_type", null: false + t.integer "product_id", null: false t.integer "address_id" - t.string "product_type" - t.integer "product_id" t.index ["product_type", "product_id"], name: "index_orders_on_product" t.index ["user_id"], name: "index_orders_on_user_id" end diff --git a/spec/factories/addresses.rb b/spec/factories/addresses.rb index bc3de5c2..27579011 100644 --- a/spec/factories/addresses.rb +++ b/spec/factories/addresses.rb @@ -4,6 +4,7 @@ city { Faker::Address.city } state { Faker::Address.state_abbr } postal_code { Faker::Address.zip_code } + save_to_user { true } association :addressable, factory: :user # Default polymorphic association end end diff --git a/spec/requests/addresses_spec.rb b/spec/requests/addresses_spec.rb new file mode 100644 index 00000000..3c366f4c --- /dev/null +++ b/spec/requests/addresses_spec.rb @@ -0,0 +1,181 @@ +require 'rails_helper' + +RSpec.describe "Addresses", type: :request do + let(:admin_user) { create(:user, :admin_user) } + let(:regular_user) { create(:user, :regular_user) } + let(:teacher_user) { create(:user, :teacher_user) } + let(:speaker_user) { create(:user, :speaker_user) } + let(:address) { create(:address, addressable: regular_user) } + + describe "POST /create" do + let(:address) { create(:address, addressable: regular_user) } + + context "when the user is regular user" do + it "creates new address" do + sign_in regular_user + address_params = { + address: { + street_address: "123 St", + city: "City1", + state: "State1", + postal_code: "12345", + save_to_user: true + } + } + expect { + post "/api/v1/users/#{regular_user.id}/addresses", params: address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(1) + expect(response).to have_http_status(:created) + end + end + + context "when the user is admin user" do + it "can create new address for user" do + sign_in admin_user + address_params = { + address: { + street_address: "123 St", + city: "City1", + state: "State1", + postal_code: "12345", + save_to_user: true + } + } + + expect { + post "/api/v1/users/#{admin_user.id}/addresses", params: address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(1) + expect(response).to have_http_status(:created) + end + end + + context "when the user is teacher_user" do + it "can create new address for teacher user" do + sign_in teacher_user + address_params = { + address: { + street_address: "123 St", + city: "City1", + state: "State1", + postal_code: "12345", + save_to_user: true + } + } + + expect { + post "/api/v1/users/#{teacher_user.id}/addresses", params: address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(1) + expect(response).to have_http_status(:created) + end + end + + context "when the user is speaker user" do + it "can create new address for speaker user" do + sign_in speaker_user + address_params = { + address: { + street_address: "123 St", + city: "City1", + state: "State1", + postal_code: "12345", + save_to_user: true + } + } + + expect { + post "/api/v1/users/#{speaker_user.id}/addresses", params: address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(1) + expect(response).to have_http_status(:created) + end + end + end + + describe "PUT /update" do + let(:address) { create(:address, addressable: regular_user) } + let(:updated_address_params) { + { address: { + street_address: "456 St", + city: "City1", + state: "State1", + postal_code: "12345", + save_to_user: true + } } + } + + context "when the user is regular user" do + it "can update their own address" do + sign_in regular_user + put "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", params: updated_address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + expect(response).to have_http_status(:ok) + end + end + + context "when the user is admin user" do + it "can update an address" do + sign_in admin_user + put "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", params: updated_address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + expect(response).to have_http_status(:ok) + end + end + + context "when the user is teacher user" do + it "can update an address" do + sign_in teacher_user + put "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", params: updated_address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + expect(response).to have_http_status(:ok) + end + end + + context "when the user is speaker user" do + it "can update an address" do + sign_in speaker_user + put "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", params: updated_address_params, headers: { 'Authorization': "Bearer #{@auth_token}" } + expect(response).to have_http_status(:ok) + end + end + end + + describe "DELETE /destroy" do + let!(:address) { create(:address, addressable: regular_user) } + + context "when the user is regular user" do + it "can delete their own address" do + sign_in regular_user + expect { + delete "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end + + context "when the user is admin user" do + it "can delete any address" do + sign_in admin_user + expect { + delete "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end + + context "when the user is teacher user" do + it "can delete an address" do + sign_in teacher_user + expect { + delete "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end + + context "when the user is speaker user" do + it "can delete an address" do + sign_in speaker_user + expect { + delete "/api/v1/users/#{regular_user.id}/addresses/#{address.id}", headers: { 'Authorization': "Bearer #{@auth_token}" } + }.to change(Address, :count).by(-1) + expect(response).to have_http_status(:no_content) + end + end + end +end