Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support for Creating Contacts with Optional Company Information #61

Merged
merged 7 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -601,7 +601,7 @@ Successful response for users without saved contacts:
```

#### Create a contact with required and optional fields.
New contacts require a unique first and last name. All other fields are optional.
***New contacts require a unique first and last name. All other fields are optional.***

Request:
```
Expand Down Expand Up @@ -644,6 +644,45 @@ Status: 201 created
}

```
#### Create a contact with a company name from the dropdown box
***New contacts with company name require a unique first and last name, and company ID in the URI. All other fields are optional.***

Request:
```
POST api/v1/users/:user_id/companies/:company_id/contacts

Authorization: Bearer Token - put in token for user

raw json body with all fields:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps adding some clarification on which of the fields are absolutely required. I saw in the tests you were using a minimal params when creating. So while it is very helpful to have the full JSON as an example, having some information on which fields will trigger the 404 error if not included can clear that up. For example, first and last name are required while email is an optional field.

Copy link
Collaborator

@reneemes reneemes Dec 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for pointing this out. Due to how the DB is currently set up only the first name, last name, and company ID id needed, depending on which URL being used. The Read Me currently states that unique first and last names are required, but does not specify that the new route needs a company ID. I can update that to make it more apparent.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Read Me has been updated.


{
"data": [
{
"id": "1",
"type": "contacts",
"attributes": {
"first_name": "Jane",
"last_name": "Doe",
"company_id": 2,
"email": "",
"phone_number": "",
"notes": "",
"user_id": 2,
"company": {
"id": 2,
"name": "Future Designs LLC",
"website": "https://futuredesigns.com",
"street_address": "456 Future Blvd",
"city": "Austin",
"state": "TX",
"zip_code": "73301",
"notes": "Submitted application for the UI Designer role."
}
}
}
]
},
```

#### Contact Errors
401 Error Response if no token provided:
Expand Down
42 changes: 20 additions & 22 deletions app/controllers/api/v1/contacts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,33 +26,31 @@ def index
end
end

def show
company = @current_user.companies.find_by(id: params[:id])
authorize company

if company
contacts = company.contacts
render json: { company: CompanySerializer.new(company), contacts: ContactSerializer.new(contacts) }
def create
authorize Contact
if params[:company_id]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to the company controller, this could potentially be refactored into the model. While not necessary for this specific PR, something to make note of, especially if there is any more logic added to the action at a later time.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, definitely. There is a ticket up for that, #58 in backlog.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ope! I'm sorry I forgot that you mentioned that in the stand up.

company = @current_user.companies.find_by(id: params[:company_id])
if company
contact = company.contacts.new(contact_params.merge(user_id: @current_user.id))
else
return render json: { error: "Company not found" }, status: :not_found
end
else
render json: { error: "Company not found or unauthorized access" }, status: :not_found
contact = @current_user.contacts.new(contact_params)
end

if contact.save
render json: ContactsSerializer.new(contact), status: :created
else
render json: { error: contact.errors.full_messages.to_sentence }, status: :unprocessable_entity
end
end

def create
authorize Contact
contact = @current_user.contacts.new(contact_params)
if contact.save
render json: ContactsSerializer.new(contact), status: :created
else
render json: { error: contact.errors.full_messages.to_sentence }, status: :unprocessable_entity
end
end
private

private

def contact_params
params.require(:contact).permit(:first_name, :last_name, :company_id, :email, :phone_number, :notes)
end
def contact_params
params.require(:contact).permit(:first_name, :last_name, :company_id, :email, :phone_number, :notes)
end
end
end
end
18 changes: 18 additions & 0 deletions app/serializers/contacts_serializer.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
class ContactsSerializer
include JSONAPI::Serializer
attributes :first_name, :last_name, :company_id, :email, :phone_number, :notes, :user_id

attribute :company do |object|
company = object.company
if company
{
id: company.id,
name: company.name,
website: company.website,
street_address: company.street_address,
city: company.city,
state: company.state,
zip_code: company.zip_code,
notes: company.notes
}
else
nil
end
end
end
8 changes: 5 additions & 3 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@
namespace :api do
namespace :v1 do
resources :users, only: [:create, :index, :show, :update] do
resources :contacts, only: [:create, :index]

resources :job_applications, only: [:create, :index, :show]
resources :companies, only: [:create, :index, :show] do
resources :contacts, only: [:index]
resources :companies, only: [:create, :index] do
resources :contacts, only: [:create, :index]
end
resources :contacts, only: [:index, :create]
resource :dashboard, only: :show
end

resources :sessions, only: :create
end
end
end

22 changes: 22 additions & 0 deletions spec/requests/api/v1/contacts/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,18 @@
expect(json[:attributes][:last_name]).to eq("Smith")
expect(json[:attributes][:phone_number]).to eq("123-555-6789")
end

it 'creates a contact when given the company ID, returns a 201' do
minimal_params = { contact: {first_name: "John", last_name: "Smith" } }
post api_v1_user_company_contacts_path(user_id: @user1.id, company_id: @company.id), params: minimal_params , headers: { "Authorization" => "Bearer #{@token}" }, as: :json

expect(response).to have_http_status(:created)
json = JSON.parse(response.body, symbolize_names: true)[:data]

expect(json[:attributes][:first_name]).to eq("John")
expect(json[:attributes][:last_name]).to eq("Smith")
expect(json[:attributes][:company][:name]).to eq("Turing")
end
end

context "when the request is invalid - Sad Paths" do
Expand Down Expand Up @@ -154,6 +166,16 @@
json = JSON.parse(response.body, symbolize_names: true)
expect(json[:error]).to eq("Phone number must be in the format '555-555-5555'")
end

it 'returns a 404 error when a company is not found by ID number' do
minimal_params = { contact: {first_name: "John", last_name: "Smith" } }
post api_v1_user_company_contacts_path(user_id: @user1.id, company_id: 99999), params: minimal_params , headers: { "Authorization" => "Bearer #{@token}" }, as: :json

expect(response).to have_http_status(:not_found)
json = JSON.parse(response.body, symbolize_names: true)

expect(json[:error]).to eq("Company not found")
end
end

context "edge cases - Sad Paths" do
Expand Down
26 changes: 26 additions & 0 deletions spec/requests/api/v1/contacts/index_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,27 @@
expect(json[:data]).to eq([])
expect(json[:message]).to eq("No contacts found")
end

it "should return 200 and all contacts associated with a company" do
get api_v1_user_company_contacts_path(user_id: @user.id, company_id: @company.id), headers: { "Authorization" => "Bearer #{@token}" }, as: :json

expect(response).to be_successful
expect(response).to have_http_status(:ok)
json = JSON.parse(response.body, symbolize_names: true)

expect(json[:company][:data][:attributes][:name]).to eq("Turing")
expect(json[:contacts][:data][0][:attributes][:last_name]).to eq("Smith")

end
end

context "Sad Paths" do
before(:each) do
@user = User.create!(name: "Me", email: "its_me", password: "reallyGoodPass")
@company = Company.create!(name: "Turing", website: "www.turing.com", street_address: "123 Main St", city: "Denver", state: "CO", zip_code: "80218", user_id: @user.id)
user_params = { email: "its_me", password: "reallyGoodPass" }
post api_v1_sessions_path, params: user_params, as: :json
@token = JSON.parse(response.body)["token"]
end

it "returns a 403 and an error message if no token is provided" do
Expand Down Expand Up @@ -76,6 +92,16 @@

expect(json[:error]).to eq("Not authenticated")
end

it "returns a 404 and an error message if company ID is not found" do
get api_v1_user_company_contacts_path(user_id: @user.id, company_id: 99999), headers: { "Authorization" => "Bearer #{@token}" }, as: :json

expect(response).to_not be_successful
expect(response).to have_http_status(:not_found)
json = JSON.parse(response.body, symbolize_names: true)

expect(json[:error]).to eq("Company not found or unauthorized access")
end
end
end
end