Skip to content

Commit

Permalink
Merge pull request #8894 from CitizenLabDotCo/TAN-2224/co-sponsors
Browse files Browse the repository at this point in the history
TAN-2224 Co-sponsors
  • Loading branch information
IvaKop authored Sep 30, 2024
2 parents b1c878e + 4b3c37b commit 0dbdbc9
Show file tree
Hide file tree
Showing 127 changed files with 1,129 additions and 154 deletions.
24 changes: 12 additions & 12 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,22 @@
# =================

build:
docker-compose build
docker compose build
cd front && npm install

reset-dev-env:
# -v removes volumes with all the data inside https://docs.docker.com/compose/reference/down/
docker-compose down -v || true # do not exit on error (some networks may be present, volumes may be used, which is often fine)
docker compose down -v || true # do not exit on error (some networks may be present, volumes may be used, which is often fine)
make build
# https://citizenlabco.slack.com/archives/C016C2EHURY/p1644234622002569
docker-compose run --rm web "bin/rails db:create && bin/rails db:reset"
docker-compose run --rm -e RAILS_ENV=test web bin/rails db:drop db:create db:schema:load
docker compose run --rm web "bin/rails db:create && bin/rails db:reset"
docker compose run --rm -e RAILS_ENV=test web bin/rails db:drop db:create db:schema:load

migrate:
docker-compose run --rm web bin/rails db:migrate cl2back:clean_tenant_settings email_campaigns:assure_campaign_records fix_existing_tenants:update_permissions cl2back:clear_cache_store email_campaigns:remove_deprecated
docker compose run --rm web bin/rails db:migrate cl2back:clean_tenant_settings email_campaigns:assure_campaign_records fix_existing_tenants:update_permissions cl2back:clear_cache_store email_campaigns:remove_deprecated

be-up:
docker-compose up
docker compose up

fe-up:
cd front && npm start
Expand All @@ -41,7 +41,7 @@ up:
# # or
# make rails-console
c rails-console:
docker-compose run --rm web bin/rails c
docker compose run --rm web bin/rails c

# Runs rails console in an existing web container. May be useful if you need to access localhost:4000 in the console.
# E.g., this command works in this console `curl http://localhost:4000`
Expand All @@ -50,7 +50,7 @@ rails-console-exec:

# search_path=localhost specifies the schema of localhost tenant
psql:
docker-compose run -it -e PGPASSWORD=postgres -e PGOPTIONS="--search_path=localhost" postgres psql -U postgres -h postgres -d cl2_back_development
docker compose run -it -e PGPASSWORD=postgres -e PGOPTIONS="--search_path=localhost" postgres psql -U postgres -h postgres -d cl2_back_development

# Run it with:
# make copy-paste-code-entity source=initiative_resubmitted_for_review target=new_cosponsor_added
Expand All @@ -64,12 +64,12 @@ blint back-lint-autocorrect:
# Usage example:
# make r file=spec/models/idea_spec.rb
r rspec:
docker-compose run --rm web bin/rspec ${file}
docker compose run --rm web bin/rspec ${file}

# Usage example:
# make feature-toggle feature=initiative_cosponsors enabled=true
feature-toggle:
docker-compose run web "bin/rails runner \"enabled = ${enabled}; feature = '${feature}'; Tenant.find_by(host: 'localhost').switch!; c = AppConfiguration.instance; c.settings['${feature}'] ||= {}; c.settings['${feature}']['allowed'] = ${enabled}; c.settings['${feature}']['enabled'] = ${enabled}; c.save!\""
docker compose run web "bin/rails runner \"enabled = ${enabled}; feature = '${feature}'; Tenant.find_by(host: 'localhost').switch!; c = AppConfiguration.instance; c.settings['${feature}'] ||= {}; c.settings['${feature}']['allowed'] = ${enabled}; c.settings['${feature}']['enabled'] = ${enabled}; c.save!\""

# =================
# E2E tests
Expand All @@ -80,8 +80,8 @@ feature-toggle:
# After running this command, start the dev servers as usual (make up)
e2e-setup:
make build
docker-compose run --rm web bin/rails db:drop db:create db:schema:load
docker-compose run --rm web bin/rails cl2_back:create_tenant[localhost,e2etests_template]
docker compose run --rm web bin/rails db:drop db:create db:schema:load
docker compose run --rm web bin/rails cl2_back:create_tenant[localhost,e2etests_template]

e2e-setup-and-up:
make e2e-setup
Expand Down
35 changes: 35 additions & 0 deletions back/app/controllers/web_api/v1/cosponsorships_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# frozen_string_literal: true

class WebApi::V1::CosponsorshipsController < ApplicationController
before_action :set_cosponsorship, except: %i[index]
skip_before_action :authenticate_user, only: %i[index]

def index
cosponsorships = policy_scope(Cosponsorship)

# Filter by idea id param
if params[:idea_id]
cosponsorships = cosponsorships.where(idea_id: params[:idea_id])
end

paginated_cosponsorships = paginate cosponsorships
render json: linked_json(paginated_cosponsorships, WebApi::V1::CosponsorshipSerializer, params: jsonapi_serializer_params)
end

def accept
if @cosponsorship.status == 'pending'
@cosponsorship.update(status: 'accepted')
# SideFxAcceptedCosponsorshipService.new.after_accept(@cosponsorship, current_user)
render json: WebApi::V1::CosponsorshipSerializer.new(@cosponsorship, params: jsonapi_serializer_params).serializable_hash
else
render json: { errors: @cosponsorship.errors.details }, status: :unprocessable_entity
end
end

private

def set_cosponsorship
@cosponsorship = Cosponsorship.find(params[:id])
authorize @cosponsorship
end
end
9 changes: 7 additions & 2 deletions back/app/controllers/web_api/v1/ideas_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ def update

extract_custom_field_values_from_params!(input.custom_form)
params[:idea][:topic_ids] ||= [] if params[:idea].key?(:topic_ids)
params[:idea][:cosponsor_ids] ||= [] if params[:idea].key?(:cosponsor_ids)
params[:idea][:phase_ids] ||= [] if params[:idea].key?(:phase_ids)
params_service.mark_custom_field_values_to_clear!(input.custom_field_values, params[:idea][:custom_field_values])

Expand Down Expand Up @@ -234,7 +235,7 @@ def update
render json: WebApi::V1::IdeaSerializer.new(
input.reload,
params: jsonapi_serializer_params,
include: %i[author topics user_reaction idea_images]
include: %i[author topics user_reaction idea_images cosponsors]
).serializable_hash, status: :ok
else
render json: { errors: input.errors.details }, status: :unprocessable_entity
Expand Down Expand Up @@ -365,6 +366,10 @@ def idea_complex_attributes(custom_form, submittable_field_keys)
complex_attributes[:topic_ids] = []
end

if submittable_field_keys.include?(:cosponsor_ids)
complex_attributes[:cosponsor_ids] = []
end

complex_attributes
end

Expand All @@ -391,7 +396,7 @@ def idea_status_not_allowed?(input)
end

def serialization_options_for(ideas)
include = %i[author idea_images ideas_phases]
include = %i[author idea_images ideas_phases cosponsors]
if current_user
# I have no idea why but the trending query part
# breaks if you don't fetch the ids in this way.
Expand Down
34 changes: 34 additions & 0 deletions back/app/models/cosponsorship.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# frozen_string_literal: true

# == Schema Information
#
# Table name: cosponsorships
#
# id :uuid not null, primary key
# status :string default("pending"), not null
# user_id :uuid not null
# idea_id :uuid not null
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
#
# index_cosponsorships_on_idea_id (idea_id)
# index_cosponsorships_on_user_id (user_id)
#
# Foreign Keys
#
# fk_rails_... (idea_id => ideas.id)
# fk_rails_... (user_id => users.id)
#
class Cosponsorship < ApplicationRecord
STATUSES = %w[pending accepted].freeze

belongs_to :user
belongs_to :idea

has_many :notifications, dependent: :nullify

validates :user, :idea, presence: true
validates :status, inclusion: { in: STATUSES }
end
6 changes: 4 additions & 2 deletions back/app/models/custom_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ class CustomField < ApplicationRecord
INPUT_TYPES = %w[
checkbox date file_upload files html html_multiloc image_files linear_scale multiline_text multiline_text_multiloc
multiselect multiselect_image number page point line polygon select select_image shapefile_upload text text_multiloc
topic_ids section
topic_ids section cosponsor_ids
].freeze
CODES = %w[
author_id birthyear body_multiloc budget domicile education gender idea_files_attributes idea_images_attributes
ideation_section1 ideation_section2 ideation_section3 location_description proposed_budget title_multiloc topic_ids
ideation_section1 ideation_section2 ideation_section3 location_description proposed_budget title_multiloc topic_ids cosponsor_ids
].freeze
VISIBLE_TO_PUBLIC = 'public'
VISIBLE_TO_ADMINS = 'admins'
Expand Down Expand Up @@ -235,6 +235,8 @@ def accept(visitor)
visitor.visit_text_multiloc self
when 'topic_ids'
visitor.visit_topic_ids self
when 'cosponsor_ids'
visitor.visit_cosponsor_ids self
else
raise "Unsupported input type: #{input_type}"
end
Expand Down
3 changes: 3 additions & 0 deletions back/app/models/idea.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ class Idea < ApplicationRecord
has_many :text_images, as: :imageable, dependent: :destroy
has_many :followers, as: :followable, dependent: :destroy

has_many :cosponsorships, dependent: :destroy
has_many :cosponsors, through: :cosponsorships, source: :user

has_many :idea_images, -> { order(:ordering) }, dependent: :destroy, inverse_of: :idea
has_many :idea_files, -> { order(:ordering) }, dependent: :destroy, inverse_of: :idea
has_one :idea_trending_info
Expand Down
2 changes: 2 additions & 0 deletions back/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ def oldest_admin
has_many :attended_events, through: :event_attendances, source: :event
has_many :follows, -> { order(:followable_id) }, class_name: 'Follower', dependent: :destroy
has_many :cosponsors_initiatives, dependent: :destroy
has_many :cosponsorships, dependent: :destroy
has_many :cosponsored_ideas, through: :cosponsorships, source: :idea

before_validation :sanitize_bio_multiloc, if: :bio_multiloc
before_validation :complete_registration
Expand Down
20 changes: 20 additions & 0 deletions back/app/policies/cosponsorship_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# frozen_string_literal: true

class CosponsorshipPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope

def initialize(user, scope)
@user = user
@scope = scope
end

def resolve
scope.where(idea_id: Pundit.policy_scope(user, Idea))
end
end

def accept?
user.id == record.user_id
end
end
8 changes: 8 additions & 0 deletions back/app/serializers/web_api/v1/cosponsorship_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# frozen_string_literal: true

class WebApi::V1::CosponsorshipSerializer < WebApi::V1::BaseSerializer
attributes :status

belongs_to :idea
belongs_to :user
end
1 change: 1 addition & 0 deletions back/app/serializers/web_api/v1/idea_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class WebApi::V1::IdeaSerializer < WebApi::V1::BaseSerializer
has_many :idea_images, serializer: WebApi::V1::ImageSerializer
has_many :phases
has_many :ideas_phases
has_many :cosponsors, record_type: :user, serializer: WebApi::V1::UserSerializer

belongs_to :author, record_type: :user, serializer: WebApi::V1::UserSerializer
belongs_to :project
Expand Down
4 changes: 4 additions & 0 deletions back/app/services/export/xlsx/value_visitor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ def visit_topic_ids(_field)
topic_titles.join(VALUE_SEPARATOR)
end

def visit_cosponsor_ids(_field)
''
end

private

attr_reader :model, :option_index, :multiloc_service
Expand Down
4 changes: 4 additions & 0 deletions back/app/services/field_visitor_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ def visit_topic_ids(field)
default(field)
end

def visit_cosponsor_ids(field)
default(field)
end

def visit_page(field)
default(field)
end
Expand Down
4 changes: 2 additions & 2 deletions back/app/services/idea_custom_fields_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@ def submittable_fields_with_other_options

# Used in the printable PDF export
def printable_fields
ignore_field_types = %w[section page date files image_files point file_upload shapefile_upload topic_ids]
ignore_field_types = %w[section page date files image_files point file_upload shapefile_upload topic_ids cosponsor_ids]
fields = enabled_fields.reject { |field| ignore_field_types.include? field.input_type }
insert_other_option_text_fields(fields)
end

def importable_fields
ignore_field_types = %w[page section date files image_files file_upload shapefile_upload point line polygon]
ignore_field_types = %w[page section date files image_files file_upload shapefile_upload point line polygon cosponsor_ids]
enabled_fields_with_other_options.reject { |field| ignore_field_types.include? field.input_type }
end

Expand Down
11 changes: 11 additions & 0 deletions back/app/services/input_json_schema_generator_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,15 @@ def visit_topic_ids(field)
end
}
end

def visit_cosponsor_ids(field)
{
type: 'array',
uniqueItems: true,
minItems: field.enabled? && field.required? ? 1 : 0,
items: {
type: 'string'
}
}
end
end
4 changes: 4 additions & 0 deletions back/app/services/input_ui_schema_generator_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ def visit_topic_ids(field)
default field
end

def visit_cosponsor_ids(field)
default field
end

def visit_page(field)
{
type: 'Page',
Expand Down
3 changes: 3 additions & 0 deletions back/config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,9 @@ en:
topic_ids:
title: Tags
description:
consponsor_ids:
title: Co-sponsors
description: Invite co-sponsors through the field below. The invited co-sponsor(s) will receive an email.
location:
title: Location
description:
Expand Down
3 changes: 3 additions & 0 deletions back/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@
defaults: { reactable: 'Idea', spam_reportable: 'Idea', post: 'Idea', followable: 'Idea', parent_param: :idea_id } do
resources :images, defaults: { container_type: 'Idea' }
resources :files, defaults: { container_type: 'Idea' }
resources :cosponsorships, defaults: { container_type: 'Idea' } do
patch 'accept', on: :member
end

get :as_xlsx, on: :collection, action: 'index_xlsx'
get :mini, on: :collection, action: 'index_mini'
Expand Down
12 changes: 12 additions & 0 deletions back/config/schemas/settings.schema.json.erb
Original file line number Diff line number Diff line change
Expand Up @@ -1322,6 +1322,18 @@
}
},

"input_cosponsorship": {
"type": "object",
"title": "Input co-sponsorship",
"description": "Enables the ability to invite co-sponsors to your inputs.",
"additionalProperties": false,
"required": ["allowed", "enabled"],
"properties": {
"allowed": { "type": "boolean", "default": false },
"enabled": { "type": "boolean", "default": false }
}
},

"management_feed": {
"type": "object",
"title": "Management Feed",
Expand Down
13 changes: 13 additions & 0 deletions back/db/migrate/20240917181018_create_cosponsorships.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# frozen_string_literal: true

class CreateCosponsorships < ActiveRecord::Migration[7.0]
def change
create_table :cosponsorships, id: :uuid do |t|
t.string :status, default: 'pending', null: false
t.references :user, foreign_key: true, type: :uuid, index: true, null: false
t.references :idea, foreign_key: true, type: :uuid, index: true, null: false

t.timestamps
end
end
end
Loading

0 comments on commit 0dbdbc9

Please sign in to comment.