diff --git a/backend/app/controllers/api/v1/members/projects_controller.rb b/backend/app/controllers/api/v1/members/projects_controller.rb new file mode 100644 index 0000000..677f7df --- /dev/null +++ b/backend/app/controllers/api/v1/members/projects_controller.rb @@ -0,0 +1,100 @@ +module API + module V1 + module Members + class ProjectsController < BaseController + include API::Pagination + + skip_before_action :require_json!, only: %i[create update] + + load_and_authorize_resource + + def index + @projects = @projects.where member: current_member + pagy_object, @projects = pagy @projects, page: current_page, items: per_page unless params[:disable_pagination].to_s == "true" + render json: ProjectSerializer.new( + @projects, + include: included_relationships, + fields: sparse_fieldset, + links: pagy_object.present? ? pagination_links(:api_v1_projects_path, pagy_object) : nil, + meta: pagy_object.present? ? pagination_meta(pagy_object) : nil, + params: {current_member: current_member, current_ability: current_ability} + ).serializable_hash + end + + def show + render json: ProjectSerializer.new( + current_member.projects.find(params[:id]), + include: included_relationships, + fields: sparse_fieldset, + params: {current_member: current_member, current_ability: current_ability} + ).serializable_hash + end + + def create + @project = Project.new create_params + if @project.save + render json: ProjectSerializer.new( + @project, + include: included_relationships, + params: {current_member: current_member, current_ability: current_ability} + ).serializable_hash + else + raise API::UnprocessableEntityError, @project.errors.full_messages.to_sentence + end + end + + def update + if @project.update update_params + render json: ProjectSerializer.new( + @project, + include: included_relationships, + params: {current_member: current_member, current_ability: current_ability} + ).serializable_hash + else + raise API::UnprocessableEntityError, @project.errors.full_messages.to_sentence + end + end + + def destroy + @project.destroy! + head :ok + rescue ActiveRecord::DeleteRestrictionError + raise API::UnprocessableEntityError, @project.errors.full_messages.to_sentence + end + + private + + def create_params + update_params + end + + def update_params + project_params.merge( + recipient_attributes: recipient_params.to_h + ) + end + + def project_params + {member_id: current_member.id} + end + + def recipient_params + params.fetch(:project_params, {}).permit( + :name, + :description, + :logo, + :contact_first_name, + :contact_last_name, + :website, + :country_id, + :state_id, + :city, + :leadership_demographics_other, + :recipient_legal_status, + leadership_demographics: [] + ) + end + end + end + end +end diff --git a/backend/app/models/ability.rb b/backend/app/models/ability.rb index d6e6fd1..a5c178e 100644 --- a/backend/app/models/ability.rb +++ b/backend/app/models/ability.rb @@ -30,6 +30,8 @@ def admin_rights def member_rights can %i[show update], Member, id: @member.id can %i[show update], Funder, id: @member.funder_id + can :manage, Project, member_id: @member.id + can :manage, Recipient, project: {member_id: @member.id} end def default_rights diff --git a/backend/app/models/member.rb b/backend/app/models/member.rb index 0502e3d..ea19421 100644 --- a/backend/app/models/member.rb +++ b/backend/app/models/member.rb @@ -3,6 +3,8 @@ class Member < ApplicationRecord belongs_to :funder + has_many :projects + validates_presence_of :first_name, :last_name validates :password, length: {minimum: 12, message: :password_length}, allow_nil: true diff --git a/backend/app/models/project.rb b/backend/app/models/project.rb index 65390a6..bbd1f11 100644 --- a/backend/app/models/project.rb +++ b/backend/app/models/project.rb @@ -2,8 +2,9 @@ class Project < ApplicationRecord include PgSearch::Model belongs_to :recipient + belongs_to :member, optional: true - has_many :investments, dependent: :destroy + has_many :investments, dependent: :restrict_with_exception has_many :funders, -> { distinct }, through: :investments has_many :investment_subgeographics, through: :investments has_many :subgeographics, -> { distinct }, through: :investment_subgeographics diff --git a/backend/config/routes/api.rb b/backend/config/routes/api.rb index 46c889b..971af00 100644 --- a/backend/config/routes/api.rb +++ b/backend/config/routes/api.rb @@ -41,6 +41,7 @@ namespace :members do resource :funder, only: %i[show update] + resources :projects end end end diff --git a/backend/db/migrate/20241231125414_add_member_to_projects.rb b/backend/db/migrate/20241231125414_add_member_to_projects.rb new file mode 100644 index 0000000..4483cdd --- /dev/null +++ b/backend/db/migrate/20241231125414_add_member_to_projects.rb @@ -0,0 +1,5 @@ +class AddMemberToProjects < ActiveRecord::Migration[7.0] + def change + add_reference :projects, :member, null: true, foreign_key: true, type: :uuid + end +end diff --git a/backend/db/schema.rb b/backend/db/schema.rb index bb66c4a..b2dfa8d 100644 --- a/backend/db/schema.rb +++ b/backend/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2024_12_25_135941) do +ActiveRecord::Schema[7.0].define(version: 2024_12_31_125414) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" enable_extension "postgis" @@ -164,6 +164,8 @@ t.uuid "recipient_id" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.uuid "member_id" + t.index ["member_id"], name: "index_projects_on_member_id" t.index ["recipient_id"], name: "index_projects_on_recipient_id" end @@ -251,6 +253,7 @@ add_foreign_key "investments", "funders", on_delete: :cascade add_foreign_key "investments", "projects", on_delete: :cascade add_foreign_key "members", "funders" + add_foreign_key "projects", "members" add_foreign_key "projects", "recipients", on_delete: :cascade add_foreign_key "recipients", "subgeographics", column: "country_id", on_delete: :cascade add_foreign_key "recipients", "subgeographics", column: "state_id", on_delete: :cascade diff --git a/backend/spec/fixtures/snapshots/api/v1/members/create-project.json b/backend/spec/fixtures/snapshots/api/v1/members/create-project.json new file mode 100644 index 0000000..a6d6b46 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/create-project.json @@ -0,0 +1,62 @@ +{ + "data": { + "id": "87af8776-4b68-4d0b-97c1-f02da0c77fe3", + "type": "project", + "attributes": { + "name": "New Project", + "description": "Description", + "website": "https://example.com", + "leadership_demographics": [ + "black_or_african_american", + "other" + ], + "leadership_demographics_other": "Something specific", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxqT0RrM1lXVmtaQzFqTmpJM0xUUm1PREF0T0ROaE5TMWxNV0poTkRjM05HSmtNek1HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--15fdb5e7fd7782bc1fed3d1e0d35b5bada98fd24/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxqT0RrM1lXVmtaQzFqTmpJM0xUUm1PREF0T0ROaE5TMWxNV0poTkRjM05HSmtNek1HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--15fdb5e7fd7782bc1fed3d1e0d35b5bada98fd24/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxqT0RrM1lXVmtaQzFqTmpJM0xUUm1PREF0T0ROaE5TMWxNV0poTkRjM05HSmtNek1HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--15fdb5e7fd7782bc1fed3d1e0d35b5bada98fd24/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "fe085f5b-4eb4-4efb-bc95-2d91d2983108", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "308bb417-d74a-441f-8663-a9477536dd56", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/get-project-include-relationships.json b/backend/spec/fixtures/snapshots/api/v1/members/get-project-include-relationships.json new file mode 100644 index 0000000..286d3f2 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/get-project-include-relationships.json @@ -0,0 +1,24 @@ +{ + "data": { + "id": "f70fc7dd-ee5d-4753-94b2-5fdb6f19eca0", + "type": "project", + "attributes": { + "name": "Otha Kemmer" + }, + "relationships": { + "subgeographics": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + "included": [ + + ] +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/get-project-sparse-fieldset.json b/backend/spec/fixtures/snapshots/api/v1/members/get-project-sparse-fieldset.json new file mode 100644 index 0000000..ebf31e7 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/get-project-sparse-fieldset.json @@ -0,0 +1,12 @@ +{ + "data": { + "id": "8054ba5f-f093-4f37-ac09-820c78538cae", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo." + }, + "relationships": { + } + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/get-project.json b/backend/spec/fixtures/snapshots/api/v1/members/get-project.json new file mode 100644 index 0000000..2f28f14 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/get-project.json @@ -0,0 +1,62 @@ +{ + "data": { + "id": "64e15dbc-529f-45a8-bc9c-5ce38c92bc2c", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo.", + "website": "http://kutch-spencer.com/moises", + "leadership_demographics": [ + "hispanic_or_latinx", + "other" + ], + "leadership_demographics_other": "Enim repellat pariatur est.", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs1TW1FMk9EVmlOaTAwWkdJeExUUmtZemt0T0Rka01DMDJabVptTnpWaFpUSXlZVGtHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--cf2f156affcb2dd92def035e31a785d838c9b7ed/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs1TW1FMk9EVmlOaTAwWkdJeExUUmtZemt0T0Rka01DMDJabVptTnpWaFpUSXlZVGtHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--cf2f156affcb2dd92def035e31a785d838c9b7ed/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs1TW1FMk9EVmlOaTAwWkdJeExUUmtZemt0T0Rka01DMDJabVptTnpWaFpUSXlZVGtHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--cf2f156affcb2dd92def035e31a785d838c9b7ed/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "4ba9e52c-2829-4a54-bab0-8cf2f8a46d09", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "d5071559-8dad-4abe-adfd-c9bf1ce0df07", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/projects-include-relationships.json b/backend/spec/fixtures/snapshots/api/v1/members/projects-include-relationships.json new file mode 100644 index 0000000..6368af6 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/projects-include-relationships.json @@ -0,0 +1,77 @@ +{ + "data": [ + { + "id": "6f04a537-9bc1-4895-851c-bc439d4a896b", + "type": "project", + "attributes": { + "name": "Otha Kemmer" + }, + "relationships": { + "subgeographics": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + { + "id": "ab126899-3b7f-4e21-b7e8-ab053782fa0f", + "type": "project", + "attributes": { + "name": "Otha Kemmer" + }, + "relationships": { + "subgeographics": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + { + "id": "753eabdf-0539-499e-a369-f8ca0f71c043", + "type": "project", + "attributes": { + "name": "Otha Kemmer" + }, + "relationships": { + "subgeographics": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } + ], + "included": [ + + ], + "meta": { + "page": 1, + "per_page": 10, + "from": 1, + "to": 3, + "total": 3, + "pages": 1 + }, + "links": { + "first": "/api/v1/projects?page%5Bsize%5D=10", + "self": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10", + "last": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10" + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/projects-sparse-fieldset.json b/backend/spec/fixtures/snapshots/api/v1/members/projects-sparse-fieldset.json new file mode 100644 index 0000000..df74fc8 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/projects-sparse-fieldset.json @@ -0,0 +1,47 @@ +{ + "data": [ + { + "id": "02b9a979-fc46-4537-a03d-6eedafbd1221", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo." + }, + "relationships": { + } + }, + { + "id": "5e00cb11-39db-41a5-8da9-d6b0888651c9", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo." + }, + "relationships": { + } + }, + { + "id": "522b143d-ad52-4f75-b8e6-33f97d551629", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo." + }, + "relationships": { + } + } + ], + "meta": { + "page": 1, + "per_page": 10, + "from": 1, + "to": 3, + "total": 3, + "pages": 1 + }, + "links": { + "first": "/api/v1/projects?page%5Bsize%5D=10", + "self": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10", + "last": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10" + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/projects.json b/backend/spec/fixtures/snapshots/api/v1/members/projects.json new file mode 100644 index 0000000..ba2ebb8 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/projects.json @@ -0,0 +1,197 @@ +{ + "data": [ + { + "id": "79487ab4-84e6-46aa-b62e-5773b2388ec6", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo.", + "website": "http://kutch-spencer.com/moises", + "leadership_demographics": [ + "hispanic_or_latinx", + "other" + ], + "leadership_demographics_other": "Enim repellat pariatur est.", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "09b700cf-8140-4d80-9e7c-a54e4a26f267", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "1d4e53e9-e3a3-4043-a52a-32ab984253fb", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + { + "id": "877b6d4d-e41c-4eff-b8a6-0d3abc00be90", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo.", + "website": "http://kutch-spencer.com/moises", + "leadership_demographics": [ + "hispanic_or_latinx", + "other" + ], + "leadership_demographics_other": "Enim repellat pariatur est.", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "09b700cf-8140-4d80-9e7c-a54e4a26f267", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "1d4e53e9-e3a3-4043-a52a-32ab984253fb", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + { + "id": "c8f894dd-7bef-4504-a038-2a1e39ee50a5", + "type": "project", + "attributes": { + "name": "Otha Kemmer", + "description": "Enim repellat pariatur. Earum modi eos. Libero tempora exercitationem. Qui dolorem quo.", + "website": "http://kutch-spencer.com/moises", + "leadership_demographics": [ + "hispanic_or_latinx", + "other" + ], + "leadership_demographics_other": "Enim repellat pariatur est.", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WmpJME9XTXpZaTB3WkdKbUxUUmhNR0V0WVdReE5pMWhPR0V6WldNNFptTXhOeklHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--1187515544cda0d8299ed5a46f566ebaaac3c1ce/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "09b700cf-8140-4d80-9e7c-a54e4a26f267", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "1d4e53e9-e3a3-4043-a52a-32ab984253fb", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } + ], + "meta": { + "page": 1, + "per_page": 10, + "from": 1, + "to": 3, + "total": 3, + "pages": 1 + }, + "links": { + "first": "/api/v1/projects?page%5Bsize%5D=10", + "self": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10", + "last": "/api/v1/projects?page%5Bnumber%5D=1&page%5Bsize%5D=10" + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/update-project-include-relationships.json b/backend/spec/fixtures/snapshots/api/v1/members/update-project-include-relationships.json new file mode 100644 index 0000000..fb8a4ee --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/update-project-include-relationships.json @@ -0,0 +1,65 @@ +{ + "data": { + "id": "4477f7bd-298a-4ee8-947e-05b1a1c67413", + "type": "project", + "attributes": { + "name": "Updated Project", + "description": "Description", + "website": "https://example.com", + "leadership_demographics": [ + "black_or_african_american", + "other" + ], + "leadership_demographics_other": "Something specific", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WWpBd04ySXlNUzFtTTJFd0xUUTNaVEl0T0RWbU55MDNZMkk1T0dOaU0yVTNZV01HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8ad7db0475dc1a0bfaf93f12ddeeb8bb794ee5e4/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WWpBd04ySXlNUzFtTTJFd0xUUTNaVEl0T0RWbU55MDNZMkk1T0dOaU0yVTNZV01HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8ad7db0475dc1a0bfaf93f12ddeeb8bb794ee5e4/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWs0WWpBd04ySXlNUzFtTTJFd0xUUTNaVEl0T0RWbU55MDNZMkk1T0dOaU0yVTNZV01HT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8ad7db0475dc1a0bfaf93f12ddeeb8bb794ee5e4/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "843eb4d9-0565-4470-807e-5c08030996e8", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "bd3ef45a-963a-4779-b5c6-5f29f9171351", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + }, + "included": [ + + ] +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/update-project-sparse-fieldset.json b/backend/spec/fixtures/snapshots/api/v1/members/update-project-sparse-fieldset.json new file mode 100644 index 0000000..5c7c547 --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/update-project-sparse-fieldset.json @@ -0,0 +1,62 @@ +{ + "data": { + "id": "beb32f0e-5dcf-4ab4-9c57-d0e6ae2aa3b5", + "type": "project", + "attributes": { + "name": "Updated Project", + "description": "Description", + "website": "https://example.com", + "leadership_demographics": [ + "black_or_african_american", + "other" + ], + "leadership_demographics_other": "Something specific", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWsxTmpNNFpHTmtOQzB5TVRGa0xUUmhPVGN0WVRJell5MHhOR0UyWkdWaVpqUTRaamdHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8d53468ca0bae0284498d2795682efb2efc9926c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWsxTmpNNFpHTmtOQzB5TVRGa0xUUmhPVGN0WVRJell5MHhOR0UyWkdWaVpqUTRaamdHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8d53468ca0bae0284498d2795682efb2efc9926c/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWsxTmpNNFpHTmtOQzB5TVRGa0xUUmhPVGN0WVRJell5MHhOR0UyWkdWaVpqUTRaamdHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--8d53468ca0bae0284498d2795682efb2efc9926c/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "1fbe2eaf-befd-4ff6-8366-b3e3e17809d2", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "6a2fe964-d146-49ea-954f-625af7e8bd11", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } +} \ No newline at end of file diff --git a/backend/spec/fixtures/snapshots/api/v1/members/update-project.json b/backend/spec/fixtures/snapshots/api/v1/members/update-project.json new file mode 100644 index 0000000..b1a765c --- /dev/null +++ b/backend/spec/fixtures/snapshots/api/v1/members/update-project.json @@ -0,0 +1,62 @@ +{ + "data": { + "id": "e286df85-8eb8-4780-a0c3-4aec5f611ddb", + "type": "project", + "attributes": { + "name": "Updated Project", + "description": "Description", + "website": "https://example.com", + "leadership_demographics": [ + "black_or_african_american", + "other" + ], + "leadership_demographics_other": "Something specific", + "areas": [ + + ], + "demographics": [ + + ], + "demographics_other": "", + "capital_types": [ + + ], + "capital_type_other": "", + "recipient_legal_status": "for_profit", + "logo": { + "small": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxpTTJNNE5ERXlNUzB4TURBd0xUUTJORFV0T0RBMFppMHlNRGN6WkdRNE1tVmpNR0VHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--f6f79eb42f50c89d31785eedbe27dc863faf672b/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERJd01IZ3lNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--8673702c2d856505736727890dcac6a632977811/picture.jpg", + "medium": "http://localhost:4000/rails/active_storage/representations/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxpTTJNNE5ERXlNUzB4TURBd0xUUTJORFV0T0RBMFppMHlNRGN6WkdRNE1tVmpNR0VHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--f6f79eb42f50c89d31785eedbe27dc863faf672b/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaDdCem9MWm05eWJXRjBTU0lJYW5CbkJqb0dSVlE2QzNKbGMybDZaVWtpRERnd01IZzRNREFHT3daVSIsImV4cCI6bnVsbCwicHVyIjoidmFyaWF0aW9uIn19--e82a96c0eabd93d914de4ef37febd98c36e0e275/picture.jpg", + "original": "http://localhost:4000/rails/active_storage/blobs/redirect/eyJfcmFpbHMiOnsibWVzc2FnZSI6IkJBaEpJaWxpTTJNNE5ERXlNUzB4TURBd0xUUTJORFV0T0RBMFppMHlNRGN6WkdRNE1tVmpNR0VHT2daRlZBPT0iLCJleHAiOm51bGwsInB1ciI6ImJsb2JfaWQifX0=--f6f79eb42f50c89d31785eedbe27dc863faf672b/picture.jpg" + } + }, + "relationships": { + "state": { + "data": { + "id": "88b86fc5-f451-42d3-a89a-7e0a58cf9daf", + "type": "subgeographic" + } + }, + "country": { + "data": { + "id": "fafef169-8683-4706-bcd4-9084f67343b4", + "type": "subgeographic" + } + }, + "subgeographics": { + "data": [ + + ] + }, + "subgeographic_ancestors": { + "data": [ + + ] + }, + "funders": { + "data": [ + + ] + } + } + } +} \ No newline at end of file diff --git a/backend/spec/requests/api/v1/members/funders_spec.rb b/backend/spec/requests/api/v1/members/funders_spec.rb index 4e7175b..396a04a 100644 --- a/backend/spec/requests/api/v1/members/funders_spec.rb +++ b/backend/spec/requests/api/v1/members/funders_spec.rb @@ -92,7 +92,11 @@ capital_types: {type: :array, items: {type: :string}}, areas: {type: :array, items: {type: :string}}, demographics: {type: :array, items: {type: :string}} - } + }, + required: %w[name description primary_office_city primary_contact_first_name primary_contact_last_name" \ + "primary_contact_email date_joined_fora number_staff_employees funder_type capital_acceptances" \ + "leadership_demographics application_status funder_legal_status capital_types areas demographics " \ + "primary_office_country_id] } parameter name: "fields[funder]", in: :formData, type: :string, description: "Get only required fields. Use comma to separate multiple fields", required: false parameter name: :includes, in: :formData, type: :string, description: "Include relationships. Use comma to separate multiple fields", required: false diff --git a/backend/spec/requests/api/v1/members/projects_spec.rb b/backend/spec/requests/api/v1/members/projects_spec.rb new file mode 100644 index 0000000..5768b87 --- /dev/null +++ b/backend/spec/requests/api/v1/members/projects_spec.rb @@ -0,0 +1,347 @@ +require "swagger_helper" + +RSpec.describe "API V1 Member Project", type: :request do + path "/api/v1/members/projects" do + get "List all projects for current member" do + tags "Projects" + consumes "application/json" + produces "application/json" + security [Bearer: {}] + + include_context "with authorization" + + parameter name: "page[number]", in: :query, type: :integer, description: "Page number. Default: 1", required: false + parameter name: "page[size]", in: :query, type: :integer, description: "Per page items. Default: 10", required: false + parameter name: :disable_pagination, in: :query, type: :boolean, description: "Turn off pagination", required: false + parameter name: "fields[project]", in: :query, type: :string, description: "Get only required fields. Use comma to separate multiple fields", required: false + parameter name: :includes, in: :query, type: :string, description: "Include relationships. Use comma to separate multiple fields", required: false + + let!(:member) { create :member } + let!(:projects) do + create_list :project, 3, recipient: create(:recipient, recipient_legal_status: "for_profit"), member: member + end + let!(:project_of_different_member) do + create :project, recipient: create(:recipient, recipient_legal_status: "government_organization") + end + + response "200", :success do + schema type: :object, properties: { + data: {type: :array, items: {"$ref" => "#/components/schemas/project"}}, + meta: {"$ref" => "#/components/schemas/pagination_meta", :nullable => true}, + links: {"$ref" => "#/components/schemas/pagination_links", :nullable => true} + } + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + + run_test! + + it_behaves_like "with pagination", expected_total: 3 + + it "matches snapshot", generate_swagger_example: true do + expect(response.body).to match_snapshot("api/v1/members/projects") + end + + context "with sparse fieldset" do + let("fields[project]") { "name,description,nonexisting" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/projects-sparse-fieldset") + end + end + + context "with relationships" do + let("fields[project]") { "name,subgeographics,funders,nonexisting" } + let(:includes) { "subgeographics,funders" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/projects-include-relationships") + end + end + + context "when disabling pagination" do + let("page[size]") { 1 } + let(:disable_pagination) { true } + + it "shows all records" do + expect(response_json["data"].size).to eq(Project.where(member: member).count) + expect(response_json["meta"]).to be_nil + expect(response_json["links"]).to be_nil + end + end + end + end + + post "Create a project for current member" do + tags "Projects" + consumes "multipart/form-data" + produces "application/json" + security [Bearer: {}] + + include_context "with authorization" + + parameter name: :project_params, in: :formData, schema: { + type: :object, + properties: { + name: {type: :string}, + description: {type: :string}, + logo: {type: :binary}, + contact_first_name: {type: :string}, + contact_last_name: {type: :string}, + website: {type: :string}, + country_id: {type: :integer}, + state_id: {type: :integer}, + city: {type: :string}, + leadership_demographics_other: {type: :string}, + recipient_legal_status: {type: :string}, + leadership_demographics: {type: :array, items: {type: :string}} + }, + required: %w[name description contact_first_name contact_last_name country_id city recipient_legal_status] + } + parameter name: "fields[project]", in: :formData, type: :string, description: "Get only required fields. Use comma to separate multiple fields", required: false + parameter name: :includes, in: :formData, type: :string, description: "Include relationships. Use comma to separate multiple fields", required: false + + let!(:member) { create :member } + let(:project_params) {} + + response "200", :success do + schema type: :object, properties: {data: {"$ref" => "#/components/schemas/project"}} + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:project_params) do + { + name: "New Project", + description: "Description", + logo: fixture_file_upload("spec/fixtures/files/picture.jpg"), + contact_first_name: "John", + contact_last_name: "Doe", + website: "https://example.com", + country_id: create(:subgeographic, geographic: :countries).id, + state_id: create(:subgeographic, geographic: :national).id, + city: "City", + leadership_demographics_other: "Something specific", + leadership_demographics: %w[black_or_african_american other], + recipient_legal_status: "for_profit" + } + end + + run_test! + + it "matches snapshot", generate_swagger_example: true do + expect(response.body).to match_snapshot("api/v1/members/create-project") + end + end + + response "422", "Invalid attributes" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:project_params) { {name: ""} } + + run_test! + end + end + end + + path "/api/v1/members/projects/{id}" do + get "Show selected project for current member" do + tags "Projects" + consumes "application/json" + produces "application/json" + security [Bearer: {}] + + include_context "with authorization" + + parameter name: :id, in: :path, type: :string, description: "Project ID", required: true + parameter name: "fields[project]", in: :query, type: :string, description: "Get only required fields. Use comma to separate multiple fields", required: false + parameter name: :includes, in: :query, type: :string, description: "Include relationships. Use comma to separate multiple fields", required: false + + let!(:member) { create :member } + let!(:project) { create :project, recipient: create(:recipient, recipient_legal_status: "for_profit"), member: member } + let!(:project_of_different_member) do + create :project, recipient: create(:recipient, recipient_legal_status: "government_organization") + end + let(:id) { project.id } + + response "200", :success do + schema type: :object, properties: {data: {"$ref" => "#/components/schemas/project"}} + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + + run_test! + + it "matches snapshot", generate_swagger_example: true do + expect(response.body).to match_snapshot("api/v1/members/get-project") + end + + context "with sparse fieldset" do + let("fields[project]") { "name,description,nonexisting" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/get-project-sparse-fieldset") + end + end + + context "with relationships" do + let("fields[project]") { "name,subgeographics,funders,nonexisting" } + let(:includes) { "subgeographics,funders" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/get-project-include-relationships") + end + end + end + + response "404", "Not found" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:id) { project_of_different_member.id } + + run_test! + end + end + + put "Update selected project for current member" do + tags "Projects" + consumes "multipart/form-data" + produces "application/json" + security [Bearer: {}] + + include_context "with authorization" + + parameter name: :id, in: :path, type: :string, description: "Project ID", required: true + parameter name: :project_params, in: :formData, schema: { + type: :object, + properties: { + name: {type: :string}, + description: {type: :string}, + logo: {type: :binary}, + contact_first_name: {type: :string}, + contact_last_name: {type: :string}, + website: {type: :string}, + country_id: {type: :integer}, + state_id: {type: :integer}, + city: {type: :string}, + leadership_demographics_other: {type: :string}, + recipient_legal_status: {type: :string}, + leadership_demographics: {type: :array, items: {type: :string}} + } + } + parameter name: "fields[project]", in: :formData, type: :string, description: "Get only required fields. Use comma to separate multiple fields", required: false + parameter name: :includes, in: :formData, type: :string, description: "Include relationships. Use comma to separate multiple fields", required: false + + let!(:member) { create :member } + let!(:project) { create :project, recipient: create(:recipient, recipient_legal_status: "for_profit"), member: member } + let!(:project_of_different_member) do + create :project, recipient: create(:recipient, recipient_legal_status: "government_organization") + end + let(:id) { project.id } + let(:project_params) {} + + response "200", :success do + schema type: :object, properties: {data: {"$ref" => "#/components/schemas/project"}} + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:project_params) do + { + name: "Updated Project", + description: "Description", + logo: fixture_file_upload("spec/fixtures/files/picture.jpg"), + contact_first_name: "John", + contact_last_name: "Doe", + website: "https://example.com", + country_id: create(:subgeographic, geographic: :countries).id, + state_id: create(:subgeographic, geographic: :national).id, + city: "City", + leadership_demographics_other: "Something specific", + leadership_demographics: %w[black_or_african_american other], + recipient_legal_status: "for_profit" + } + end + + run_test! + + it "matches snapshot", generate_swagger_example: true do + expect(response.body).to match_snapshot("api/v1/members/update-project") + end + + context "with sparse fieldset" do + let("fields[project]") { "name,description,nonexisting" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/update-project-sparse-fieldset") + end + end + + context "with relationships" do + let("fields[project]") { "name,subgeographics,funders,nonexisting" } + let(:includes) { "subgeographics,funders" } + + it "matches snapshot" do + expect(response.body).to match_snapshot("api/v1/members/update-project-include-relationships") + end + end + end + + response "403", "Cannot update project of different member" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:id) { project_of_different_member.id } + + run_test! + end + + response "422", "Invalid attributes" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:project_params) { {name: ""} } + + run_test! + end + end + + delete "Delete selected project for current member" do + tags "Projects" + consumes "application/json" + produces "application/json" + security [Bearer: {}] + + include_context "with authorization" + + parameter name: :id, in: :path, type: :string, description: "Project ID", required: true + + let!(:member) { create :member } + let!(:project) { create :project, recipient: create(:recipient, recipient_legal_status: "for_profit"), member: member } + let!(:project_of_different_member) do + create :project, recipient: create(:recipient, recipient_legal_status: "government_organization") + end + let(:id) { project.id } + + response "200", :success do + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + + run_test! + end + + response "403", "Cannot delete project of different member" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let(:id) { project_of_different_member.id } + + run_test! + end + + response "422", "Project with investments cannot be deleted" do + schema "$ref" => "#/components/schemas/errors" + + let(:Authorization) { "Bearer #{JWTAuth.encode(member)}" } + let!(:investment) { create :investment, project: project } + + run_test! + end + end + end +end