From f784ce5acba17172158860f3a46e83af1672e2c5 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 1 Jul 2025 09:57:34 +0100 Subject: [PATCH 01/42] issue/3532 - Changed Create Account View. Moved email to top. --- app/views/shared/_create_account_form.html.erb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/views/shared/_create_account_form.html.erb b/app/views/shared/_create_account_form.html.erb index 9fd4afd56d..2d0d7be057 100644 --- a/app/views/shared/_create_account_form.html.erb +++ b/app/views/shared/_create_account_form.html.erb @@ -1,5 +1,8 @@ <%= form_for resource, namespace: 'new', url: registration_path("user"), html: {autocomplete: "off", id: "create_account_form"} do |f| %> - +
+ <%= f.label(:email, _('Email'), class: "form-label") %> + <%= f.email_field(:email, class: "form-control", "aria-required": true) %> +
<%= f.label(:firstname, _('First Name'), class: "form-label") %> <%= f.text_field(:firstname, class: "form-control", "aria-required": true) %> @@ -8,10 +11,6 @@ <%= f.label(:surname, _('Last Name'), class: "form-label") %> <%= f.text_field(:surname, class: "form-control", "aria-required": true) %>
-
- <%= f.label(:email, _('Email'), class: "form-label") %> - <%= f.email_field(:email, class: "form-control", "aria-required": true) %> -
<%= render partial: org_partial, locals: { From 39e0acaff893fc071dd96efffba98c2cbb38edb2 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 1 Jul 2025 10:47:29 +0100 Subject: [PATCH 02/42] Created an OrgsByDomainController that in the first iteration returnd dummy Json Array data. --- .../api/orgs_by_domain_controller.rb | 28 +++++++++++++++++++ config/routes.rb | 2 ++ .../api/orgs_by_domain_controller_test.rb | 7 +++++ 3 files changed, 37 insertions(+) create mode 100644 app/controllers/api/orgs_by_domain_controller.rb create mode 100644 test/controllers/api/orgs_by_domain_controller_test.rb diff --git a/app/controllers/api/orgs_by_domain_controller.rb b/app/controllers/api/orgs_by_domain_controller.rb new file mode 100644 index 0000000000..b150533948 --- /dev/null +++ b/app/controllers/api/orgs_by_domain_controller.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module Api + # Controller for API routes that return orgs by domain. + class OrgsByDomainController < ApplicationController + def index + dummy_orgs = [ + { + ror_id: 'abc123def', + org_name: 'University of California', + domain: 'berkeley.edu' + }, + { + ror_id: 'xyz789ghi', + org_name: 'Massachusetts Institute of Technology', + domain: 'mit.edu' + }, + { + ror_id: 'mno456pqr', + org_name: 'Stanford University', + domain: 'stanford.edu' + } + ] + + render json: dummy_orgs + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 5954d44591..7ea2e2698f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -203,6 +203,8 @@ resources :plans, only: %i[create show index] resources :templates, only: [:index] end + + get 'get-orgs-by-domain', to: 'orgs_by_domain#index' end namespace :paginable do diff --git a/test/controllers/api/orgs_by_domain_controller_test.rb b/test/controllers/api/orgs_by_domain_controller_test.rb new file mode 100644 index 0000000000..7d4c494238 --- /dev/null +++ b/test/controllers/api/orgs_by_domain_controller_test.rb @@ -0,0 +1,7 @@ +require "test_helper" + +class Api::OrgsByDomainControllerTest < ActionDispatch::IntegrationTest + # test "the truth" do + # assert true + # end +end From 260bf78b40061a8b439046bfd25c149182df4d24 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 1 Jul 2025 11:28:19 +0100 Subject: [PATCH 03/42] issue/3532 - Updated OrgsDomainController. Changes: - Added Strong Parameters to ensure only domain is permitted in api call /api/get-orgs-by-domain?domain=some_domain. - Dummy data functionality: - Gets the domain parameter from the request (params[:domain]). - Filters the dummy data to find organizations matching the provided domain - Returns matching organizations if found for domains: berkeley.edu, mit.edu and stanford.edu. - Returns the "OTHER" organization if no matches are found. - Returns null if domain param missing. --- .../api/orgs_by_domain_controller.rb | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/orgs_by_domain_controller.rb b/app/controllers/api/orgs_by_domain_controller.rb index b150533948..9978fdade5 100644 --- a/app/controllers/api/orgs_by_domain_controller.rb +++ b/app/controllers/api/orgs_by_domain_controller.rb @@ -3,26 +3,56 @@ module Api # Controller for API routes that return orgs by domain. class OrgsByDomainController < ApplicationController + # GET /api/get-orgs-by-domain?domain=berkeley.edu def index + domain_param = search_params[:domain] + dummy_orgs = [ { - ror_id: 'abc123def', + id: 'abc123def', org_name: 'University of California', domain: 'berkeley.edu' }, { - ror_id: 'xyz789ghi', + id: 'xyz789ghi', org_name: 'Massachusetts Institute of Technology', domain: 'mit.edu' }, { - ror_id: 'mno456pqr', + id: 'mno456pqr', org_name: 'Stanford University', domain: 'stanford.edu' } ] - render json: dummy_orgs + # Filter orgs by domain if domain parameter is provided + if domain_param.present? + filtered_orgs = dummy_orgs.select { |org| org[:domain] == domain_param } + + # If no matches found, return the "OTHER" org + if filtered_orgs.empty? + other_org = [ + { + ror_id: 'OTHER', + org_name: 'OTHER', + domain: 'OTHER' + } + ] + render json: other_org + else + render json: filtered_orgs + end + else + # If no domain parameter provided, return all dummy orgs + render json: other_org + end + end + + private + + # Using Strong Parameters ensure only domain is permitted + def search_params + params.permit(:domain) end end end From 3d34d08c4f11abc1e4718917c913e1d7e98248eb Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 1 Jul 2025 13:05:44 +0100 Subject: [PATCH 04/42] issue/3532 - Added functionality to display Org in Create Account page. - Currently Javascript is in _create_account_form.html.erb. This will need to be moved and improved upon. - Selection will return Other for all domains except mit.edu, stanford.edu and berkley.edu. --- .../api/orgs_by_domain_controller.rb | 2 +- .../shared/_create_account_form.html.erb | 92 +++++++++++++++++-- 2 files changed, 86 insertions(+), 8 deletions(-) diff --git a/app/controllers/api/orgs_by_domain_controller.rb b/app/controllers/api/orgs_by_domain_controller.rb index 9978fdade5..bc7b127ca7 100644 --- a/app/controllers/api/orgs_by_domain_controller.rb +++ b/app/controllers/api/orgs_by_domain_controller.rb @@ -52,7 +52,7 @@ def index # Using Strong Parameters ensure only domain is permitted def search_params - params.permit(:domain) + params.permit(:domain, :format) end end end diff --git a/app/views/shared/_create_account_form.html.erb b/app/views/shared/_create_account_form.html.erb index 2d0d7be057..720c4bf803 100644 --- a/app/views/shared/_create_account_form.html.erb +++ b/app/views/shared/_create_account_form.html.erb @@ -12,13 +12,8 @@ <%= f.text_field(:surname, class: "form-control", "aria-required": true) %>
- <%= render partial: org_partial, - locals: { - form: f, - orgs: orgs, - default_org: resource&.org, - required: true - } %> + <%= f.label(:org_name, _('Organisation'), class: "form-label") %> + <%= f.select(:org_id, [], { prompt: _('Select an organisation') }, { class: "form-control" }) %>
<%= f.label(:password, _('Password'), class: "form-label") %> @@ -47,3 +42,86 @@ <% end %> <%= f.button(_('Create account'), class: "btn btn-secondary", type: "submit") %> <% end %> + + From a2aeac2516a6da7fc381cf2a80f9fa0c1bf74765 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Thu, 3 Jul 2025 11:24:37 +0100 Subject: [PATCH 05/42] Updated Create Account style bu removing the .form-control class from the div enclosing the input and input fields. --- app/views/shared/_create_account_form.html.erb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/shared/_create_account_form.html.erb b/app/views/shared/_create_account_form.html.erb index 720c4bf803..2eb5b4c8e2 100644 --- a/app/views/shared/_create_account_form.html.erb +++ b/app/views/shared/_create_account_form.html.erb @@ -1,31 +1,31 @@ <%= form_for resource, namespace: 'new', url: registration_path("user"), html: {autocomplete: "off", id: "create_account_form"} do |f| %> -
+
<%= f.label(:email, _('Email'), class: "form-label") %> <%= f.email_field(:email, class: "form-control", "aria-required": true) %>
-
+
<%= f.label(:firstname, _('First Name'), class: "form-label") %> <%= f.text_field(:firstname, class: "form-control", "aria-required": true) %>
-
+
<%= f.label(:surname, _('Last Name'), class: "form-label") %> <%= f.text_field(:surname, class: "form-control", "aria-required": true) %>
-
+
<%= f.label(:org_name, _('Organisation'), class: "form-label") %> <%= f.select(:org_id, [], { prompt: _('Select an organisation') }, { class: "form-control" }) %>
-
+
<%= f.label(:password, _('Password'), class: "form-label") %> <%= f.password_field(:password, class: "form-control", "aria-required": true) %>
-
+
<%= label_tag('passwords_toggle_sign_up', _('Show password'), class: 'form-check-label') %>
-
+
<%= f.label(:accept_terms) do %> <%= f.check_box(:accept_terms, "aria-required": true) %> @@ -35,7 +35,7 @@
<% if Rails.configuration.x.recaptcha.enabled %> -
+
<%= label_tag(nil, _('Security check'), class: "form-label") %> <%= recaptcha_tags %>
From c9f9769070777ccf4e33d3ccaa138d90cc268623 Mon Sep 17 00:00:00 2001 From: Andrea Davanzo Date: Fri, 4 Jul 2025 08:13:05 +0100 Subject: [PATCH 06/42] update form html --- .../shared/_create_account_form.html.erb | 103 +++++++++++------- app/views/shared/_sign_in_form.html.erb | 48 ++++---- 2 files changed, 90 insertions(+), 61 deletions(-) diff --git a/app/views/shared/_create_account_form.html.erb b/app/views/shared/_create_account_form.html.erb index 2eb5b4c8e2..7112316652 100644 --- a/app/views/shared/_create_account_form.html.erb +++ b/app/views/shared/_create_account_form.html.erb @@ -1,43 +1,62 @@ <%= form_for resource, namespace: 'new', url: registration_path("user"), html: {autocomplete: "off", id: "create_account_form"} do |f| %> -
- <%= f.label(:email, _('Email'), class: "form-label") %> - <%= f.email_field(:email, class: "form-control", "aria-required": true) %> +
+
+ <%= f.label(:email, _('Email'), class: "form-label") %> + <%= f.email_field(:email, class: "form-control", "aria-required": true) %> +
-
- <%= f.label(:firstname, _('First Name'), class: "form-label") %> - <%= f.text_field(:firstname, class: "form-control", "aria-required": true) %> + +
+
+ <%= f.label(:firstname, _('First Name'), class: "form-label") %> + <%= f.text_field(:firstname, class: "form-control", "aria-required": true) %> +
-
- <%= f.label(:surname, _('Last Name'), class: "form-label") %> - <%= f.text_field(:surname, class: "form-control", "aria-required": true) %> + +
+
+ <%= f.label(:surname, _('Last Name'), class: "form-label") %> + <%= f.text_field(:surname, class: "form-control", "aria-required": true) %> +
-
- <%= f.label(:org_name, _('Organisation'), class: "form-label") %> - <%= f.select(:org_id, [], { prompt: _('Select an organisation') }, { class: "form-control" }) %> + +
+
+ <%= f.label(:org_name, _('Organisation'), class: "form-label") %> + <%= f.select(:org_id, [], { prompt: _('Select an organisation') }, { class: "form-control" }) %> +
-
- <%= f.label(:password, _('Password'), class: "form-label") %> - <%= f.password_field(:password, class: "form-control", "aria-required": true) %> + +
+
+ <%= f.label(:password, _('Password'), class: "form-label") %> + <%= f.password_field(:password, class: "form-control", "aria-required": true) %> +
-
-
- - <%= label_tag('passwords_toggle_sign_up', _('Show password'), class: 'form-check-label') %> + +
+
+ + <%= label_tag('passwords_toggle_sign_up', _('Show password'), class: 'form-check-label') %>
-
-
- <%= f.label(:accept_terms) do %> - <%= f.check_box(:accept_terms, "aria-required": true) %> - <%= _('I accept the') %> - <%= link_to _('terms and conditions'), terms_path %> + +
+
+ <%= f.label(:accept_terms, class: 'form-check-label') do %> + <%= f.check_box(:accept_terms, "aria-required": true, class: "form-check-input") %> + <%= _('I accept the') %> + <%= link_to _('terms and conditions'), terms_path %> <% end %>
+ <% if Rails.configuration.x.recaptcha.enabled %> -
- <%= label_tag(nil, _('Security check'), class: "form-label") %> - <%= recaptcha_tags %> +
+
+ <%= label_tag(nil, _('Security check'), class: "form-label") %> + <%= recaptcha_tags %> +
<% end %> <%= f.button(_('Create account'), class: "btn btn-secondary", type: "submit") %> @@ -46,37 +65,37 @@ From 84e8201643912aa62dd2267d5cfcab1ade1a72a7 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 8 Jul 2025 17:33:33 +0100 Subject: [PATCH 08/42] Added to models OrgDomain and OrgRor. Created migration files and updated schema.rb and seeds.rb. --- app/models/org_domain.rb | 5 + app/models/org_ror.rb | 7 + .../20250708101217_create_org_domains.rb | 10 + db/migrate/20250708102008_create_org_rors.rb | 13 + db/schema.rb | 303 +++++++++++++++++- db/seeds.rb | 65 ++++ 6 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 app/models/org_domain.rb create mode 100644 app/models/org_ror.rb create mode 100644 db/migrate/20250708101217_create_org_domains.rb create mode 100644 db/migrate/20250708102008_create_org_rors.rb diff --git a/app/models/org_domain.rb b/app/models/org_domain.rb new file mode 100644 index 0000000000..3ed87a790c --- /dev/null +++ b/app/models/org_domain.rb @@ -0,0 +1,5 @@ +# frozen_string_literal: true +class OrgDomain < ApplicationRecord + belongs_to :org + +end diff --git a/app/models/org_ror.rb b/app/models/org_ror.rb new file mode 100644 index 0000000000..449bdae044 --- /dev/null +++ b/app/models/org_ror.rb @@ -0,0 +1,7 @@ + + +class OrgRor < ApplicationRecord + belongs_to :org + + +end diff --git a/db/migrate/20250708101217_create_org_domains.rb b/db/migrate/20250708101217_create_org_domains.rb new file mode 100644 index 0000000000..f931e8b37e --- /dev/null +++ b/db/migrate/20250708101217_create_org_domains.rb @@ -0,0 +1,10 @@ +class CreateOrgDomains < ActiveRecord::Migration[7.1] + def change + create_table :org_domains do |t| + t.references :org, foreign_key: true, null: false + t.text :domain, null: false + + t.timestamps # Automatically adds created_at and updated_at with default non-null constraint. + end + end +end diff --git a/db/migrate/20250708102008_create_org_rors.rb b/db/migrate/20250708102008_create_org_rors.rb new file mode 100644 index 0000000000..e810c84cb2 --- /dev/null +++ b/db/migrate/20250708102008_create_org_rors.rb @@ -0,0 +1,13 @@ +class CreateOrgRors < ActiveRecord::Migration[7.1] + def change + create_table :org_rors do |t| + t.references :org, foreign_key: true, null: false + t.text :ror_id, null: false + + t.timestamps # Automatically adds created_at and updated_at with default non-null constraint. + end + end +end + + + diff --git a/db/schema.rb b/db/schema.rb index 3b6036f2bf..41c672a846 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,10 +10,25 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2025_01_15_102816) do +ActiveRecord::Schema[7.1].define(version: 2025_07_08_102008) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "admin_users", id: :serial, force: :cascade do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at", precision: nil + t.datetime "remember_created_at", precision: nil + t.integer "sign_in_count", default: 0 + t.datetime "current_sign_in_at", precision: nil + t.datetime "last_sign_in_at", precision: nil + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + create_table "annotations", id: :serial, force: :cascade do |t| t.integer "question_id" t.integer "org_id" @@ -42,6 +57,12 @@ t.index ["user_id"], name: "fk_rails_584be190c2" end + create_table "answers_options", id: false, force: :cascade do |t| + t.integer "answer_id", null: false + t.integer "option_id", null: false + t.index ["answer_id", "option_id"], name: "index_answers_options_on_answer_id_and_option_id" + end + create_table "answers_question_options", id: false, force: :cascade do |t| t.integer "answer_id", null: false t.integer "question_option_id", null: false @@ -69,6 +90,13 @@ t.index ["name"], name: "index_oauth_applications_on_name" end + create_table "api_servers", force: :cascade do |t| + t.string "title" + t.string "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "conditions", id: :serial, force: :cascade do |t| t.integer "question_id" t.text "option_list" @@ -106,6 +134,18 @@ t.index ["org_id"], name: "index_departments_on_org_id" end + create_table "dmptemplates", id: :serial, force: :cascade do |t| + t.string "title" + t.text "description" + t.boolean "published" + t.integer "user_id" + t.integer "organisation_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.string "locale" + t.boolean "is_default" + end + create_table "exported_plans", id: :serial, force: :cascade do |t| t.integer "plan_id" t.integer "user_id" @@ -130,6 +170,37 @@ t.index ["user_id"], name: "index_external_api_access_tokens_on_user_id" end + create_table "file_types", id: :serial, force: :cascade do |t| + t.string "name" + t.string "icon_name" + t.integer "icon_size" + t.string "icon_location" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "file_uploads", id: :serial, force: :cascade do |t| + t.string "name" + t.string "title" + t.text "description" + t.integer "size" + t.boolean "published" + t.string "location" + t.integer "file_type_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "friendly_id_slugs", id: :serial, force: :cascade do |t| + t.string "slug", null: false + t.integer "sluggable_id", null: false + t.string "sluggable_type", limit: 40 + t.datetime "created_at", precision: nil + t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", unique: true + t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" + t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" + end + create_table "guidance_groups", id: :serial, force: :cascade do |t| t.string "name" t.integer "org_id" @@ -140,6 +211,12 @@ t.index ["org_id"], name: "index_guidance_groups_on_org_id" end + create_table "guidance_in_group", id: false, force: :cascade do |t| + t.integer "guidance_id", null: false + t.integer "guidance_group_id", null: false + t.index ["guidance_id", "guidance_group_id"], name: "index_guidance_in_group_on_guidance_id_and_guidance_group_id" + end + create_table "guidances", id: :serial, force: :cascade do |t| t.text "text" t.integer "guidance_group_id" @@ -246,6 +323,81 @@ t.boolean "enabled", default: true end + create_table "oauth_access_grants", force: :cascade do |t| + t.bigint "resource_owner_id", null: false + t.bigint "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false + t.datetime "created_at", precision: nil, null: false + t.datetime "revoked_at", precision: nil + t.index ["application_id"], name: "index_oauth_access_grants_on_application_id" + t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id" + t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true + end + + create_table "oauth_access_tokens", force: :cascade do |t| + t.bigint "resource_owner_id" + t.bigint "application_id", null: false + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" + t.string "scopes" + t.datetime "created_at", precision: nil, null: false + t.datetime "revoked_at", precision: nil + t.string "previous_refresh_token", default: "", null: false + t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id" + t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true + t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id" + t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true + end + + create_table "oauth_applications", force: :cascade do |t| + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false + t.boolean "confidential", default: true, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true + end + + create_table "option_warnings", id: :serial, force: :cascade do |t| + t.integer "organisation_id" + t.integer "option_id" + t.text "text" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "options", id: :serial, force: :cascade do |t| + t.integer "question_id" + t.string "text" + t.integer "number" + t.boolean "is_default" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "org_domains", force: :cascade do |t| + t.bigint "org_id", null: false + t.text "domain", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["org_id"], name: "index_org_domains_on_org_id" + end + + create_table "org_rors", force: :cascade do |t| + t.bigint "org_id", null: false + t.text "ror_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["org_id"], name: "index_org_rors_on_org_id" + end + create_table "org_token_permissions", id: :serial, force: :cascade do |t| t.integer "org_id" t.integer "token_permission_type_id" @@ -255,6 +407,30 @@ t.index ["token_permission_type_id"], name: "fk_rails_2aa265f538" end + create_table "organisation_types", id: :serial, force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "organisations", id: :serial, force: :cascade do |t| + t.string "name" + t.string "abbreviation" + t.text "description" + t.string "target_url" + t.integer "logo_file_id" + t.integer "banner_file_id" + t.integer "organisation_type_id" + t.string "domain" + t.integer "wayfless_entity" + t.integer "stylesheet_file_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.integer "parent_id" + t.boolean "is_other" + end + create_table "orgs", id: :serial, force: :cascade do |t| t.string "name" t.string "abbreviation" @@ -280,6 +456,20 @@ t.index ["region_id"], name: "fk_rails_5a6adf6bab" end + create_table "pages", id: :serial, force: :cascade do |t| + t.string "title" + t.text "body_text" + t.string "slug" + t.integer "menu" + t.integer "menu_position" + t.string "target_url" + t.string "location" + t.boolean "public" + t.integer "organisation_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + create_table "perms", id: :serial, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: nil, null: false @@ -299,6 +489,15 @@ t.index ["versionable_id"], name: "index_phases_on_versionable_id" end + create_table "plan_sections", id: :serial, force: :cascade do |t| + t.integer "user_id" + t.integer "section_id" + t.integer "plan_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.datetime "release_time", precision: nil + end + create_table "plans", id: :serial, force: :cascade do |t| t.string "title" t.integer "template_id" @@ -341,6 +540,40 @@ t.integer "user_id" end + create_table "project_groups", id: :serial, force: :cascade do |t| + t.boolean "project_creator" + t.boolean "project_editor" + t.integer "user_id" + t.integer "project_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.boolean "project_administrator" + end + + create_table "project_guidance", id: false, force: :cascade do |t| + t.integer "project_id", null: false + t.integer "guidance_group_id", null: false + t.index ["project_id", "guidance_group_id"], name: "index_project_guidance_on_project_id_and_guidance_group_id" + end + + create_table "projects", id: :serial, force: :cascade do |t| + t.string "title" + t.text "note" + t.boolean "locked" + t.integer "dmptemplate_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.string "slug" + t.integer "organisation_id" + t.string "grant_number" + t.string "identifier" + t.string "description" + t.string "principal_investigator" + t.string "principal_investigator_identifier" + t.string "data_contact" + t.index ["slug"], name: "index_projects_on_slug", unique: true + end + create_table "question_format_labels", id: false, force: :cascade do |t| t.integer "id" t.string "description" @@ -359,6 +592,16 @@ t.integer "formattype", default: 0 end + create_table "question_identifiers", force: :cascade do |t| + t.integer "question_id" + t.string "value" + t.string "name" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "versionable_id", limit: 36 + t.index ["versionable_id"], name: "index_question_identifiers_on_versionable_id" + end + create_table "question_options", id: :serial, force: :cascade do |t| t.integer "question_id" t.string "text" @@ -533,6 +776,14 @@ t.index ["subscriber_id", "subscriber_type", "plan_id"], name: "index_subscribers_on_identifiable_and_plan_id" end + create_table "suggested_answers", id: :serial, force: :cascade do |t| + t.integer "question_id" + t.integer "organisation_id" + t.text "text" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + create_table "templates", id: :serial, force: :cascade do |t| t.string "title" t.text "description" @@ -584,6 +835,35 @@ t.index ["org_id"], name: "index_trackers_on_org_id" end + create_table "user_org_roles", id: :serial, force: :cascade do |t| + t.integer "user_id" + t.integer "organisation_id" + t.integer "user_role_type_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "user_role_types", id: :serial, force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "user_statuses", id: :serial, force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + + create_table "user_types", id: :serial, force: :cascade do |t| + t.string "name" + t.text "description" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + end + create_table "users", id: :serial, force: :cascade do |t| t.string "firstname" t.string "surname" @@ -632,6 +912,23 @@ t.index ["user_id"], name: "index_users_perms_on_user_id" end + create_table "users_roles", id: false, force: :cascade do |t| + t.integer "user_id" + t.integer "role_id" + t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id" + end + + create_table "versions", id: :serial, force: :cascade do |t| + t.string "title" + t.text "description" + t.integer "published" + t.integer "number" + t.integer "phase_id" + t.datetime "created_at", precision: nil + t.datetime "updated_at", precision: nil + t.index ["phase_id"], name: "index_versions_on_phase_id" + end + add_foreign_key "annotations", "orgs" add_foreign_key "annotations", "questions" add_foreign_key "answers", "plans" @@ -644,6 +941,10 @@ add_foreign_key "notes", "users" add_foreign_key "notification_acknowledgements", "notifications" add_foreign_key "notification_acknowledgements", "users" + add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id" + add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id" + add_foreign_key "org_domains", "orgs" + add_foreign_key "org_rors", "orgs" add_foreign_key "org_token_permissions", "orgs" add_foreign_key "org_token_permissions", "token_permission_types" add_foreign_key "orgs", "languages" diff --git a/db/seeds.rb b/db/seeds.rb index 8b4edfc3aa..be5021381b 100755 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -832,3 +832,68 @@ question: Question.find_by(text: "What types of data will you collect and how will it be stored?")}, ] annotations.each{ |s| Annotation.find_or_create_by(s) if Annotation.find_by(text: s[:text]).nil? } + +#------------------------------------------------------------ + +org_domains = [ + { + org: Org.find_by(abbreviation: 'CC'), + domain: 'cc.org' + }, + { + org: Org.find_by(abbreviation: 'CC'), + domain: 'cc.edu' + }, + { + org: Org.find_by(abbreviation: 'CC'), + domain: 'cc.com' + }, + { + org: Org.find_by(abbreviation: 'GA'), + domain: 'ga.gov' + }, + { + org: Org.find_by(abbreviation: 'UOS'), + domain: 'example.edu' + }, + { + org: Org.find_by(abbreviation: 'UOS'), + domain: 'example.ac.ex' + } +] +org_domains.each{ |s| OrgDomain.find_or_create_by(s) } + +#-------------------------------------------------------------- +org_rors = [ + { + org: Org.find_by(abbreviation: 'CC'), + ror_id: 'r12345x' + }, + { + org: Org.find_by(abbreviation: 'CC'), + ror_id: 'r987654y' + }, + { + org: Org.find_by(abbreviation: 'CC'), + ror_id: 'r556789x' + }, + { + org: Org.find_by(abbreviation: 'GA'), + ror_id: 'r567890y' + }, + { + org: Org.find_by(abbreviation: 'UOS'), + ror_id: 'r45678f' + }, + { + org: Org.find_by(abbreviation: 'UOS'), + ror_id: 'r7890uy' + }, + { + org: Org.find_by(abbreviation: 'UOS'), + ror_id: 'r98970y' + }, +] + +org_rors.each{ |s| OrgRor.find_or_create_by(s) } +#--------------------------------------------------------- \ No newline at end of file From 7a5fa01c845f60ad4fe219cae498361362f09af7 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Tue, 8 Jul 2025 18:59:50 +0100 Subject: [PATCH 09/42] Move domain filtering code from JS to controller --- .../api/orgs_by_domain_controller.rb | 9 +++++---- .../src/shared/createAccountForm.js | 19 ++++++++----------- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/orgs_by_domain_controller.rb b/app/controllers/api/orgs_by_domain_controller.rb index bc7b127ca7..8a8cd9329f 100644 --- a/app/controllers/api/orgs_by_domain_controller.rb +++ b/app/controllers/api/orgs_by_domain_controller.rb @@ -5,7 +5,8 @@ module Api class OrgsByDomainController < ApplicationController # GET /api/get-orgs-by-domain?domain=berkeley.edu def index - domain_param = search_params[:domain] + email_param = search_params[:email] + email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') dummy_orgs = [ { @@ -26,8 +27,8 @@ def index ] # Filter orgs by domain if domain parameter is provided - if domain_param.present? - filtered_orgs = dummy_orgs.select { |org| org[:domain] == domain_param } + if email_param.present? + filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } # If no matches found, return the "OTHER" org if filtered_orgs.empty? @@ -52,7 +53,7 @@ def index # Using Strong Parameters ensure only domain is permitted def search_params - params.permit(:domain, :format) + params.permit(:email, :format) end end end diff --git a/app/javascript/src/shared/createAccountForm.js b/app/javascript/src/shared/createAccountForm.js index 1f2deef391..6eac0850d9 100644 --- a/app/javascript/src/shared/createAccountForm.js +++ b/app/javascript/src/shared/createAccountForm.js @@ -12,7 +12,7 @@ $(() => { return; } - let currentDomain = ''; + let currentEmail = ''; let debounceTimer = null; // Handle email input changes @@ -20,12 +20,9 @@ $(() => { const email = this.value; if (email && email.includes('@')) { - const domain = email.split('@')[1]; - - // Only fetch if domain has changed - if (domain && domain !== currentDomain) { - currentDomain = domain; - + if (email !== currentEmail) { + currentEmail = email; + // Clear previous timer if (debounceTimer) { clearTimeout(debounceTimer); @@ -33,18 +30,18 @@ $(() => { // Debounce API calls to avoid too many requests debounceTimer = setTimeout(() => { - fetchOrganizations(domain); + fetchOrganizations(email); }, 500); } } else { // Clear organizations if email is invalid - currentDomain = ''; + currentEmail = ''; resetOrgSelect(); } }); - function fetchOrganizations(domain) { - fetch(`/api/get-orgs-by-domain?domain=${encodeURIComponent(domain)}`) + function fetchOrganizations(email) { + fetch(`/api/get-orgs-by-domain?email=${encodeURIComponent(email)}`) .then(response => response.json()) .then(data => { populateOrgSelect(data); From d125ae8241825be35ffc0351f58966794809a928 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Wed, 9 Jul 2025 11:22:52 +0100 Subject: [PATCH 10/42] Annotated new models OrgDomain and OrgRor with schema information. --- app/models/org_domain.rb | 19 +++++++++++++++++++ app/models/org_ror.rb | 20 +++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/app/models/org_domain.rb b/app/models/org_domain.rb index 3ed87a790c..8dd66e1c41 100644 --- a/app/models/org_domain.rb +++ b/app/models/org_domain.rb @@ -1,4 +1,23 @@ # frozen_string_literal: true + +# == Schema Information +# +# Table name: org_domains +# +# id :bigint(8) not null, primary key +# domain :text not null +# created_at :datetime not null +# updated_at :datetime not null +# org_id :bigint(8) not null +# +# Indexes +# +# index_org_domains_on_org_id (org_id) +# +# Foreign Keys +# +# fk_rails_... (org_id => orgs.id) +# class OrgDomain < ApplicationRecord belongs_to :org diff --git a/app/models/org_ror.rb b/app/models/org_ror.rb index 449bdae044..c1f1f736f5 100644 --- a/app/models/org_ror.rb +++ b/app/models/org_ror.rb @@ -1,5 +1,23 @@ +# frozen_string_literal: true - +# == Schema Information +# +# Table name: org_rors +# +# id :bigint(8) not null, primary key +# created_at :datetime not null +# updated_at :datetime not null +# org_id :bigint(8) not null +# ror_id :text not null +# +# Indexes +# +# index_org_rors_on_org_id (org_id) +# +# Foreign Keys +# +# fk_rails_... (org_id => orgs.id) +# class OrgRor < ApplicationRecord belongs_to :org From d5d1b0ca3c34fdb4bbb8b6b0a62858f265a1fedd Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 10 Jul 2025 10:40:24 +0100 Subject: [PATCH 11/42] Change controller name for consistency with model. --- .../{orgs_by_domain_controller.rb => org_domain_controller.rb} | 2 +- config/routes.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/controllers/api/{orgs_by_domain_controller.rb => org_domain_controller.rb} (96%) diff --git a/app/controllers/api/orgs_by_domain_controller.rb b/app/controllers/api/org_domain_controller.rb similarity index 96% rename from app/controllers/api/orgs_by_domain_controller.rb rename to app/controllers/api/org_domain_controller.rb index 8a8cd9329f..8eded4f4eb 100644 --- a/app/controllers/api/orgs_by_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -2,7 +2,7 @@ module Api # Controller for API routes that return orgs by domain. - class OrgsByDomainController < ApplicationController + class OrgDomainController < ApplicationController # GET /api/get-orgs-by-domain?domain=berkeley.edu def index email_param = search_params[:email] diff --git a/config/routes.rb b/config/routes.rb index 7ea2e2698f..5ad3f816da 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -204,7 +204,7 @@ resources :templates, only: [:index] end - get 'get-orgs-by-domain', to: 'orgs_by_domain#index' + get 'get-orgs-by-domain', to: 'org_domain#index' end namespace :paginable do From 942fc7f6f5f5e21d397d7928ef80836197c52e1e Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 10 Jul 2025 11:54:38 +0100 Subject: [PATCH 12/42] Update OrgDomain controller to render json data for search based on domain. --- app/controllers/api/org_domain_controller.rb | 50 ++++++++------------ app/models/org.rb | 3 +- app/models/org_domain.rb | 7 +++ 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 8eded4f4eb..eb94d2585c 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -8,44 +8,32 @@ def index email_param = search_params[:email] email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') - dummy_orgs = [ - { - id: 'abc123def', - org_name: 'University of California', - domain: 'berkeley.edu' - }, - { - id: 'xyz789ghi', - org_name: 'Massachusetts Institute of Technology', - domain: 'mit.edu' - }, - { - id: 'mno456pqr', - org_name: 'Stanford University', - domain: 'stanford.edu' - } - ] - # Filter orgs by domain if domain parameter is provided if email_param.present? - filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } + # filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } + org_results = OrgDomain.search_with_org_info(email_domain) + result = org_results.map { |record| + { + id: record.id, + org_name: record.org_name, + domain: record.domain + } + } - # If no matches found, return the "OTHER" org - if filtered_orgs.empty? - other_org = [ + if result.empty? + pattern = "Other" + other_org = Org.where("LOWER(orgs.name) = ?", pattern.downcase) + result = other_org.map {|record| { - ror_id: 'OTHER', - org_name: 'OTHER', - domain: 'OTHER' + id: record.id, + org_name: record.name, + domain: "" } - ] - render json: other_org - else - render json: filtered_orgs + } end + render json: result else - # If no domain parameter provided, return all dummy orgs - render json: other_org + render json: [], status: :ok end end diff --git a/app/models/org.rb b/app/models/org.rb index 92122900e7..38e18e68d3 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -88,6 +88,8 @@ class Org < ApplicationRecord has_many :departments + has_many :org_domains + # =============== # = Validations = # =============== @@ -175,7 +177,6 @@ def check_for_missing_logo_file 6 => :school, column: 'org_type', check_for_column: !Rails.env.test? - # The default Org is the one whose guidance is auto-attached to # plans when a plan is created def self.default_orgs diff --git a/app/models/org_domain.rb b/app/models/org_domain.rb index 8dd66e1c41..b12da73d42 100644 --- a/app/models/org_domain.rb +++ b/app/models/org_domain.rb @@ -21,4 +21,11 @@ class OrgDomain < ApplicationRecord belongs_to :org + def self.search_with_org_info(domain) + pattern = "#{domain.downcase}" + joins(:org) + .where("LOWER(org_domains.domain) = ?", pattern) + .select("orgs.id AS id, orgs.name AS org_name, org_domains.domain") + end + end From 3fb9129c4eab29629507fa17329b0a089b25310b Mon Sep 17 00:00:00 2001 From: John Pinto Date: Thu, 10 Jul 2025 13:41:36 +0100 Subject: [PATCH 13/42] Updated select input in create form. If there is one org then it it automatically selected. If there is no org returned we do not show prompt. If there is more than one org we have propmt with text "Select an organisation". --- .../src/shared/createAccountForm.js | 24 ++++++++++++------- .../shared/_create_account_form.html.erb | 2 +- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/app/javascript/src/shared/createAccountForm.js b/app/javascript/src/shared/createAccountForm.js index 6eac0850d9..fddabfa64f 100644 --- a/app/javascript/src/shared/createAccountForm.js +++ b/app/javascript/src/shared/createAccountForm.js @@ -16,13 +16,13 @@ $(() => { let debounceTimer = null; // Handle email input changes - emailField.addEventListener('input', function() { + emailField.addEventListener('input', function () { const email = this.value; if (email && email.includes('@')) { if (email !== currentEmail) { currentEmail = email; - + // Clear previous timer if (debounceTimer) { clearTimeout(debounceTimer); @@ -56,23 +56,29 @@ $(() => { // Clear existing options orgSelect.innerHTML = ''; - // Add prompt option - const promptOption = document.createElement('option'); - promptOption.value = ''; - promptOption.textContent = 'Select an organisation'; - orgSelect.appendChild(promptOption); + if (orgs.length > 1) { + // Add prompt option + const promptOption = document.createElement('option'); + promptOption.value = ''; + promptOption.textContent = 'Select an organisation'; + orgSelect.appendChild(promptOption); + } // Add organization options - orgs.forEach(function(org) { + orgs.forEach(function (org) { const option = document.createElement('option'); option.value = org.id || org.ror_id; option.textContent = org.org_name; orgSelect.appendChild(option); }); + // Only select option if only one + if (orgs.length === 1) { + orgSelect.selectedIndex = 0; + } } function resetOrgSelect() { - orgSelect.innerHTML = ''; + orgSelect.innerHTML = ''; } }); diff --git a/app/views/shared/_create_account_form.html.erb b/app/views/shared/_create_account_form.html.erb index 2e3e760a96..b0c2bd9741 100644 --- a/app/views/shared/_create_account_form.html.erb +++ b/app/views/shared/_create_account_form.html.erb @@ -23,7 +23,7 @@
<%= f.label(:org_name, _('Organisation'), class: "form-label") %> - <%= f.select(:org_id, [], { prompt: _('Select an organisation') }, { class: "form-control" }) %> + <%= f.select(:org_id, [], { prompt: '' }, { class: "form-control" }) %>
From 2606651da3dd5984e76b5b137bdef15d4049e8ff Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 10 Jul 2025 13:52:01 +0100 Subject: [PATCH 14/42] Add seeds and schema file with Other org --- db/schema.rb | 283 --------------------------------------------------- db/seeds.rb | 7 +- 2 files changed, 6 insertions(+), 284 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 41c672a846..ec59391c88 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -14,21 +14,6 @@ # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" - create_table "admin_users", id: :serial, force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" - t.datetime "reset_password_sent_at", precision: nil - t.datetime "remember_created_at", precision: nil - t.integer "sign_in_count", default: 0 - t.datetime "current_sign_in_at", precision: nil - t.datetime "last_sign_in_at", precision: nil - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - create_table "annotations", id: :serial, force: :cascade do |t| t.integer "question_id" t.integer "org_id" @@ -57,12 +42,6 @@ t.index ["user_id"], name: "fk_rails_584be190c2" end - create_table "answers_options", id: false, force: :cascade do |t| - t.integer "answer_id", null: false - t.integer "option_id", null: false - t.index ["answer_id", "option_id"], name: "index_answers_options_on_answer_id_and_option_id" - end - create_table "answers_question_options", id: false, force: :cascade do |t| t.integer "answer_id", null: false t.integer "question_option_id", null: false @@ -90,13 +69,6 @@ t.index ["name"], name: "index_oauth_applications_on_name" end - create_table "api_servers", force: :cascade do |t| - t.string "title" - t.string "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - create_table "conditions", id: :serial, force: :cascade do |t| t.integer "question_id" t.text "option_list" @@ -134,18 +106,6 @@ t.index ["org_id"], name: "index_departments_on_org_id" end - create_table "dmptemplates", id: :serial, force: :cascade do |t| - t.string "title" - t.text "description" - t.boolean "published" - t.integer "user_id" - t.integer "organisation_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.string "locale" - t.boolean "is_default" - end - create_table "exported_plans", id: :serial, force: :cascade do |t| t.integer "plan_id" t.integer "user_id" @@ -170,37 +130,6 @@ t.index ["user_id"], name: "index_external_api_access_tokens_on_user_id" end - create_table "file_types", id: :serial, force: :cascade do |t| - t.string "name" - t.string "icon_name" - t.integer "icon_size" - t.string "icon_location" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "file_uploads", id: :serial, force: :cascade do |t| - t.string "name" - t.string "title" - t.text "description" - t.integer "size" - t.boolean "published" - t.string "location" - t.integer "file_type_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "friendly_id_slugs", id: :serial, force: :cascade do |t| - t.string "slug", null: false - t.integer "sluggable_id", null: false - t.string "sluggable_type", limit: 40 - t.datetime "created_at", precision: nil - t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", unique: true - t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" - t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" - end - create_table "guidance_groups", id: :serial, force: :cascade do |t| t.string "name" t.integer "org_id" @@ -211,12 +140,6 @@ t.index ["org_id"], name: "index_guidance_groups_on_org_id" end - create_table "guidance_in_group", id: false, force: :cascade do |t| - t.integer "guidance_id", null: false - t.integer "guidance_group_id", null: false - t.index ["guidance_id", "guidance_group_id"], name: "index_guidance_in_group_on_guidance_id_and_guidance_group_id" - end - create_table "guidances", id: :serial, force: :cascade do |t| t.text "text" t.integer "guidance_group_id" @@ -323,65 +246,6 @@ t.boolean "enabled", default: true end - create_table "oauth_access_grants", force: :cascade do |t| - t.bigint "resource_owner_id", null: false - t.bigint "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false - t.datetime "created_at", precision: nil, null: false - t.datetime "revoked_at", precision: nil - t.index ["application_id"], name: "index_oauth_access_grants_on_application_id" - t.index ["resource_owner_id"], name: "index_oauth_access_grants_on_resource_owner_id" - t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true - end - - create_table "oauth_access_tokens", force: :cascade do |t| - t.bigint "resource_owner_id" - t.bigint "application_id", null: false - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" - t.string "scopes" - t.datetime "created_at", precision: nil, null: false - t.datetime "revoked_at", precision: nil - t.string "previous_refresh_token", default: "", null: false - t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id" - t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true - t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id" - t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true - end - - create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false - t.boolean "confidential", default: true, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true - end - - create_table "option_warnings", id: :serial, force: :cascade do |t| - t.integer "organisation_id" - t.integer "option_id" - t.text "text" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "options", id: :serial, force: :cascade do |t| - t.integer "question_id" - t.string "text" - t.integer "number" - t.boolean "is_default" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - create_table "org_domains", force: :cascade do |t| t.bigint "org_id", null: false t.text "domain", null: false @@ -407,30 +271,6 @@ t.index ["token_permission_type_id"], name: "fk_rails_2aa265f538" end - create_table "organisation_types", id: :serial, force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "organisations", id: :serial, force: :cascade do |t| - t.string "name" - t.string "abbreviation" - t.text "description" - t.string "target_url" - t.integer "logo_file_id" - t.integer "banner_file_id" - t.integer "organisation_type_id" - t.string "domain" - t.integer "wayfless_entity" - t.integer "stylesheet_file_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.integer "parent_id" - t.boolean "is_other" - end - create_table "orgs", id: :serial, force: :cascade do |t| t.string "name" t.string "abbreviation" @@ -456,20 +296,6 @@ t.index ["region_id"], name: "fk_rails_5a6adf6bab" end - create_table "pages", id: :serial, force: :cascade do |t| - t.string "title" - t.text "body_text" - t.string "slug" - t.integer "menu" - t.integer "menu_position" - t.string "target_url" - t.string "location" - t.boolean "public" - t.integer "organisation_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - create_table "perms", id: :serial, force: :cascade do |t| t.string "name" t.datetime "created_at", precision: nil, null: false @@ -489,15 +315,6 @@ t.index ["versionable_id"], name: "index_phases_on_versionable_id" end - create_table "plan_sections", id: :serial, force: :cascade do |t| - t.integer "user_id" - t.integer "section_id" - t.integer "plan_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.datetime "release_time", precision: nil - end - create_table "plans", id: :serial, force: :cascade do |t| t.string "title" t.integer "template_id" @@ -540,40 +357,6 @@ t.integer "user_id" end - create_table "project_groups", id: :serial, force: :cascade do |t| - t.boolean "project_creator" - t.boolean "project_editor" - t.integer "user_id" - t.integer "project_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.boolean "project_administrator" - end - - create_table "project_guidance", id: false, force: :cascade do |t| - t.integer "project_id", null: false - t.integer "guidance_group_id", null: false - t.index ["project_id", "guidance_group_id"], name: "index_project_guidance_on_project_id_and_guidance_group_id" - end - - create_table "projects", id: :serial, force: :cascade do |t| - t.string "title" - t.text "note" - t.boolean "locked" - t.integer "dmptemplate_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.string "slug" - t.integer "organisation_id" - t.string "grant_number" - t.string "identifier" - t.string "description" - t.string "principal_investigator" - t.string "principal_investigator_identifier" - t.string "data_contact" - t.index ["slug"], name: "index_projects_on_slug", unique: true - end - create_table "question_format_labels", id: false, force: :cascade do |t| t.integer "id" t.string "description" @@ -592,16 +375,6 @@ t.integer "formattype", default: 0 end - create_table "question_identifiers", force: :cascade do |t| - t.integer "question_id" - t.string "value" - t.string "name" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "versionable_id", limit: 36 - t.index ["versionable_id"], name: "index_question_identifiers_on_versionable_id" - end - create_table "question_options", id: :serial, force: :cascade do |t| t.integer "question_id" t.string "text" @@ -776,14 +549,6 @@ t.index ["subscriber_id", "subscriber_type", "plan_id"], name: "index_subscribers_on_identifiable_and_plan_id" end - create_table "suggested_answers", id: :serial, force: :cascade do |t| - t.integer "question_id" - t.integer "organisation_id" - t.text "text" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - create_table "templates", id: :serial, force: :cascade do |t| t.string "title" t.text "description" @@ -835,35 +600,6 @@ t.index ["org_id"], name: "index_trackers_on_org_id" end - create_table "user_org_roles", id: :serial, force: :cascade do |t| - t.integer "user_id" - t.integer "organisation_id" - t.integer "user_role_type_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "user_role_types", id: :serial, force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "user_statuses", id: :serial, force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - - create_table "user_types", id: :serial, force: :cascade do |t| - t.string "name" - t.text "description" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - end - create_table "users", id: :serial, force: :cascade do |t| t.string "firstname" t.string "surname" @@ -912,23 +648,6 @@ t.index ["user_id"], name: "index_users_perms_on_user_id" end - create_table "users_roles", id: false, force: :cascade do |t| - t.integer "user_id" - t.integer "role_id" - t.index ["user_id", "role_id"], name: "index_users_roles_on_user_id_and_role_id" - end - - create_table "versions", id: :serial, force: :cascade do |t| - t.string "title" - t.text "description" - t.integer "published" - t.integer "number" - t.integer "phase_id" - t.datetime "created_at", precision: nil - t.datetime "updated_at", precision: nil - t.index ["phase_id"], name: "index_versions_on_phase_id" - end - add_foreign_key "annotations", "orgs" add_foreign_key "annotations", "questions" add_foreign_key "answers", "plans" @@ -941,8 +660,6 @@ add_foreign_key "notes", "users" add_foreign_key "notification_acknowledgements", "notifications" add_foreign_key "notification_acknowledgements", "users" - add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id" - add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id" add_foreign_key "org_domains", "orgs" add_foreign_key "org_rors", "orgs" add_foreign_key "org_token_permissions", "orgs" diff --git a/db/seeds.rb b/db/seeds.rb index be5021381b..d48d521514 100755 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -252,7 +252,12 @@ abbreviation: 'UOS', org_type: 1, links: {"org":[]}, language: default_language, region: region, - is_other: false, managed: true} + is_other: false, managed: true}, + {name: 'Other', + abbreviation: 'Other', + org_type: 3, links: {"org":[]}, + language: default_language, region: region, + is_other: true, managed: false} ] orgs.each { |o| Org.create!(o) unless Org.find_by(name: o[:name]).present? } From 3016f2e7309928d55d5f8f453b389cb31282d1e3 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 10 Jul 2025 14:40:49 +0100 Subject: [PATCH 15/42] Add OrionService and make call in OrgDomainController --- app/controllers/api/org_domain_controller.rb | 5 ++ app/services/external_apis/orion_service.rb | 49 ++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 app/services/external_apis/orion_service.rb diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index eb94d2585c..663d0aecd1 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -12,6 +12,11 @@ def index if email_param.present? # filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } org_results = OrgDomain.search_with_org_info(email_domain) + + # Call OrionService to search by domain + orion_response = ::ExternalApis::OrionService.search_by_domain(email_domain) + puts orion_response + result = org_results.map { |record| { id: record.id, diff --git a/app/services/external_apis/orion_service.rb b/app/services/external_apis/orion_service.rb new file mode 100644 index 0000000000..17fcd69384 --- /dev/null +++ b/app/services/external_apis/orion_service.rb @@ -0,0 +1,49 @@ +# app/services/external_api/orion_service.rb + +require 'net/http' +require 'uri' +require 'json' + +module ExternalApis + class OrionService + ORION_URL = "http://74.220.18.40:8080/submit" + + def self.search_by_ror_id(ror_id) + return { error: 'Missing ROR ID' } if ror_id.blank? + + payload = { + cmd: "search_by_ror_id", + value: [ror_id] + } + + post_to_orion(payload) + end + + def self.search_by_domain(domain) + return { error: 'Missing domain' } if domain.blank? + + payload = { + cmd: "search_by_domain", + value: domain + } + + post_to_orion(payload) + end + + def self.post_to_orion(payload) + uri = URI.parse(ORION_URL) + + response = Net::HTTP.post( + uri, + payload.to_json, + { "Content-Type" => "application/json" } + ) + + JSON.parse(response.body) + rescue JSON::ParserError + { error: 'Invalid response from Orion' } + rescue => e + { error: e.message } + end + end +end From 6dc558b8ad3be0ab9bc7bf76ad8fa540aaedae02 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 10 Jul 2025 17:34:29 +0100 Subject: [PATCH 16/42] Add code to retrieve and display org name and ID using OrionService --- app/controllers/api/org_domain_controller.rb | 35 +++++++++++++------- app/services/external_apis/orion_service.rb | 2 +- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 663d0aecd1..7a45ee04ea 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -13,10 +13,6 @@ def index # filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } org_results = OrgDomain.search_with_org_info(email_domain) - # Call OrionService to search by domain - orion_response = ::ExternalApis::OrionService.search_by_domain(email_domain) - puts orion_response - result = org_results.map { |record| { id: record.id, @@ -26,15 +22,30 @@ def index } if result.empty? - pattern = "Other" - other_org = Org.where("LOWER(orgs.name) = ?", pattern.downcase) - result = other_org.map {|record| - { - id: record.id, - org_name: record.name, - domain: "" - } + #---------Call OrionService to search by domain + ror_id = ::ExternalApis::OrionService.search_by_domain(email_domain) + full_org_json = ::ExternalApis::OrionService.search_by_ror_id(ror_id[0]) + # Extract the value for "Digital Curation Centre" + result = full_org_json.map do |org| + next unless org.is_a?(Hash) + + # Find title from names + title = org["names"]&.find { |n| n["types"]&.include?("label") }&.dig("value") + + # Use org id as-is + id = org["id"].split("/").last + + # Get first domain, if any + domain = org["domains"]&.first + + # Return structured hash + { + id: id, + org_name: title, + domain: domain || "" } + end.compact + #---------Orion service code end end render json: result else diff --git a/app/services/external_apis/orion_service.rb b/app/services/external_apis/orion_service.rb index 17fcd69384..e4fdc3eec8 100644 --- a/app/services/external_apis/orion_service.rb +++ b/app/services/external_apis/orion_service.rb @@ -38,7 +38,7 @@ def self.post_to_orion(payload) payload.to_json, { "Content-Type" => "application/json" } ) - + JSON.parse(response.body) rescue JSON::ParserError { error: 'Invalid response from Orion' } From 4dae8a800775c8fe57e6ce2df2c57fbde4ef761e Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Fri, 11 Jul 2025 11:28:19 +0100 Subject: [PATCH 17/42] Add code to parse api response with multiple orgs --- app/controllers/api/org_domain_controller.rb | 29 ++++++-------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 7a45ee04ea..5b3b45c820 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -23,28 +23,17 @@ def index if result.empty? #---------Call OrionService to search by domain - ror_id = ::ExternalApis::OrionService.search_by_domain(email_domain) - full_org_json = ::ExternalApis::OrionService.search_by_ror_id(ror_id[0]) + full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) # Extract the value for "Digital Curation Centre" - result = full_org_json.map do |org| - next unless org.is_a?(Hash) - - # Find title from names - title = org["names"]&.find { |n| n["types"]&.include?("label") }&.dig("value") - - # Use org id as-is - id = org["id"].split("/").last - - # Get first domain, if any - domain = org["domains"]&.first - - # Return structured hash - { - id: id, - org_name: title, - domain: domain || "" + result = full_org_json["orgs"].map do |org| + title = org["names"].find { |n| n["types"].include?("ror_display") } + + { + id: org["id"].split('/').last, + org_name: title ? title["value"] : "Name not found", + domain: "" } - end.compact + end #---------Orion service code end end render json: result From 3b068cf435960cdf7144ebea8d51a17fa1495b1a Mon Sep 17 00:00:00 2001 From: John Pinto Date: Mon, 21 Jul 2025 13:35:51 +0100 Subject: [PATCH 18/42] Updated OrgDomainController, Routes and createAccountForm.js to use a Post request for retreiving Org Domains. --- app/controllers/api/org_domain_controller.rb | 66 +++++++++++-------- .../src/shared/createAccountForm.js | 31 +++++++-- config/routes.rb | 2 +- 3 files changed, 68 insertions(+), 31 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 5b3b45c820..4318031588 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -3,50 +3,64 @@ module Api # Controller for API routes that return orgs by domain. class OrgDomainController < ApplicationController - # GET /api/get-orgs-by-domain?domain=berkeley.edu + + # PUTS /api/orgs-by-domain with parameter email. + #TBD: Change these Rubocop Cops + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def index email_param = search_params[:email] email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') + render json: [], status: :ok if email_domain.blank? # Filter orgs by domain if domain parameter is provided - if email_param.present? - # filtered_orgs = dummy_orgs.select { |org| org[:domain] == email_domain } - org_results = OrgDomain.search_with_org_info(email_domain) - - result = org_results.map { |record| - { - id: record.id, - org_name: record.org_name, - domain: record.domain - } + org_results = OrgDomain.search_with_org_info(email_domain) + result = org_results.map { |record| + { + id: record.id, + org_name: record.org_name, + domain: record.domain, } + } - if result.empty? - #---------Call OrionService to search by domain + unless result.empty? + puts "result: #{result}" + render json: result, status: :ok + return + end + + #---------Orion service code start + begin full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) + puts "full_org_json: #{full_org_json}" + + unless full_org_json&.key?('orgs') + puts 'Invalid response or no orgs key found' + render json: [], status: :ok + return + end + # Extract the value for "Digital Curation Centre" - result = full_org_json["orgs"].map do |org| - title = org["names"].find { |n| n["types"].include?("ror_display") } - - { - id: org["id"].split('/').last, - org_name: title ? title["value"] : "Name not found", - domain: "" + result = full_org_json['orgs'].map do |org| + title = org['names'].find { |n| n['types'].include?('ror_display') } + { + id: org['id'].split('/').last, + org_name: title ? title['value'] : 'Name not found', + domain: '', } - end - #---------Orion service code end - end - render json: result - else + rescue => e + puts "Failed request: #{e.message}" render json: [], status: :ok + end end + render json: result, status: :ok end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity private # Using Strong Parameters ensure only domain is permitted def search_params - params.permit(:email, :format) + params.permit(:email, :format, :org_domain) end end end diff --git a/app/javascript/src/shared/createAccountForm.js b/app/javascript/src/shared/createAccountForm.js index fddabfa64f..3df5dfe5a3 100644 --- a/app/javascript/src/shared/createAccountForm.js +++ b/app/javascript/src/shared/createAccountForm.js @@ -41,14 +41,37 @@ $(() => { }); function fetchOrganizations(email) { - fetch(`/api/get-orgs-by-domain?email=${encodeURIComponent(email)}`) + // fetch(`/api/orgs-by-domain?email=${encodeURIComponent(email)}`) + // .then(response => response.json()) + // .then(data => { + // populateOrgSelect(data); + // }) + // .catch(error => { + // console.error('Error fetching organizations:', error); + // resetOrgSelect(); + // }); + // Prepare header and body information for a POST request + // Retrieve CSRF token stored in tag + const csrftoken = document.querySelector('meta[name="csrf-token"]').getAttribute('content'); + const requestOptions = { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add X-CSRF-Token header for protection against CRSF attacks. + 'X-CSRF-Token': csrftoken, + }, + body: JSON.stringify({ email }) + }; + + // Use Fetch API with POST configuration included in requestOptions + fetch('/api/orgs-by-domain', requestOptions) .then(response => response.json()) .then(data => { - populateOrgSelect(data); + populateOrgSelect(data); }) .catch(error => { - console.error('Error fetching organizations:', error); - resetOrgSelect(); + console.error('Error fetching organizations:', error); + resetOrgSelect(); }); } diff --git a/config/routes.rb b/config/routes.rb index 5ad3f816da..38803cc150 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -204,7 +204,7 @@ resources :templates, only: [:index] end - get 'get-orgs-by-domain', to: 'org_domain#index' + post 'orgs-by-domain', to: 'org_domain#index' end namespace :paginable do From 196cf5c4d8b85b8fac290c0afed86840a6cda236 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Tue, 22 Jul 2025 18:26:08 +0100 Subject: [PATCH 19/42] Add other as option --- app/controllers/api/org_domain_controller.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 4318031588..d8750258ec 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -35,7 +35,12 @@ def index unless full_org_json&.key?('orgs') puts 'Invalid response or no orgs key found' - render json: [], status: :ok + result = [{ + id: 12345, + org_name: 'Other', + domain: '' + }] + render json: result, status: :ok return end @@ -49,7 +54,16 @@ def index } rescue => e puts "Failed request: #{e.message}" - render json: [], status: :ok + result = [] + end + + # Fallback if still no results + if result.blank? + result = [{ + id: 12345, + org_name: 'Other', + domain: '' + }] end end render json: result, status: :ok From d5d77f8feb1b2457e373cb4c20b5836cfb6c476a Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 23 Jul 2025 10:48:28 +0100 Subject: [PATCH 20/42] Add query to search for other org in db --- app/controllers/api/org_domain_controller.rb | 10 ++++++---- app/models/org.rb | 5 +++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index d8750258ec..de542890a9 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -35,9 +35,10 @@ def index unless full_org_json&.key?('orgs') puts 'Invalid response or no orgs key found' + other_org = Org.find_other_org result = [{ - id: 12345, - org_name: 'Other', + id: other_org.id, + org_name: other_org.name, domain: '' }] render json: result, status: :ok @@ -59,9 +60,10 @@ def index # Fallback if still no results if result.blank? + other_org = Org.find_other_org result = [{ - id: 12345, - org_name: 'Other', + id: other_org.id, + org_name: other_org.name, domain: '' }] end diff --git a/app/models/org.rb b/app/models/org.rb index 38e18e68d3..7868001904 100644 --- a/app/models/org.rb +++ b/app/models/org.rb @@ -183,6 +183,11 @@ def self.default_orgs where(abbreviation: Rails.configuration.x.organisation.abbreviation) end + # Returns the Org record with the name 'Other' + def self.find_other_org + where("LOWER(name) = ?", 'other').first + end + # The managed flag is set by a Super Admin. A managed org typically has # at least one Org Admini and can have associated Guidance and Templates scope :managed, -> { where(managed: true) } From 214aed4875562c47a5ca053ea13fb5e0b648f21c Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 23 Jul 2025 12:38:45 +0100 Subject: [PATCH 21/42] First round with new concern. --- app/controllers/api/org_domain_controller.rb | 2 +- .../concerns/org_creation_with_orion.rb | 131 ++++++++++++++++++ app/controllers/registrations_controller.rb | 3 +- 3 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 app/controllers/concerns/org_creation_with_orion.rb diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index de542890a9..bfa584ac7f 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -45,7 +45,7 @@ def index return end - # Extract the value for "Digital Curation Centre" + # Extract the value result = full_org_json['orgs'].map do |org| title = org['names'].find { |n| n['types'].include?('ror_display') } { diff --git a/app/controllers/concerns/org_creation_with_orion.rb b/app/controllers/concerns/org_creation_with_orion.rb new file mode 100644 index 0000000000..3813e6588b --- /dev/null +++ b/app/controllers/concerns/org_creation_with_orion.rb @@ -0,0 +1,131 @@ +# frozen_string_literal: true + +# Provides methods to handle the org_id hash returned to the controller +# for pages that use the Org selection autocomplete widget +# +# This Concern handles the incoming params from a page that has one of the +# Org Typeahead boxes found in app/views/shared/org_selectors/. +# +# The incoming hash looks like this: +# { +# "org_name"=>"Portland State University (PDX)", +# "org_sources"=>"[ +# \"3E (Belgium) (3e.eu)\", +# \"etc.\" +# ]", +# "org_crosswalk"=>"[ +# { +# \"id\":1574, +# \"name\":\"3E (Belgium) (3e.eu)\", +# \"sort_name\":\"3E\", +# \"ror\":\"https://ror.org/03d33vh19\" +# }, +# { +# "etc." +# }]", +# "id"=>"{ +# \"id\":62, +# \"name\":\"Portland State University (PDX)\", +# \"sort_name\":\"Portland State University\", +# \"ror\":\"https://ror.org/00yn2fy02\", +# \"fundref\":\"https://doi.org/10.13039/100007083\" +# } +# } +# +# The :org_name, :org_sources, :org_crosswalk are all relics of the JS involved in +# handling the request/response from OrgsController#search AJAX action that is +# used to search both the local DB and the ROR API as the user types. +# :org_name = the value the user has types in +# :org_sources = the pick list of Org names returned by the OrgsController#search action +# :org_crosswalk = all of the info about each Org returned by the OrgsController#search action +# there is JS that takes the value in :org_name and then sets the :id param +# to the matching Org in the :org_crosswalk on form submission +# +# They are typically removed from the incoming params hash prior to doing a :save or :update +# by the :remove_org_selection_params below. +# TODO: Consider adding a JS method that strips those 3 params out prior to form submission +# since we only need the contents of the :id param here +# +# The contents of :id are then used to either Create or Find the Org from the DB. +# if id: { :id } is present then the Org was one pulled from the DB. If it is not +# present then it is one of the following: +# if :ror or :fundref are present then it was one retrieved from the ROR API +# otherwise it is a free text value entered by the user +# +# See the comments on OrgsController#search for more info on how the typeaheads work +module OrgCreationWithOrion + extend ActiveSupport::Concern + + # rubocop:disable Metrics/BlockLength + included do + + private + + # Converts the incoming params_into an Org by either locating it + # via its id, identifier and/or name, or initializing a new one + # the default allow_create is based off restrict_orgs + def org_from_params(params_in:, + allow_create: !Rails.configuration.x.application.restrict_orgs) + # params_in = params_in.with_indifferent_access + return nil unless params_in[:org_id].present? && + params_in[:org_id].is_a?(String) + + hash = org_hash_from_params(params_in: params_in) + return nil unless hash.present? + + #todo: need to create a function to create org hash + org = {"name": "University of Stanford (uofa.edu)"} + # org = OrgSelection::HashToOrgService.to_org(hash: hash, + # allow_create: allow_create) + allow_create ? create_org(org: org, params_in: params_in) : org + end + + # Converts the incoming params_into an array of Identifiers + def identifiers_from_params(params_in:) + # params_in = params_in.to_h.with_indifferent_access + return [] unless params_in[:org_id].present? && + params_in[:org_id].is_a?(String) + + hash = org_hash_from_params(params_in: params_in) + return [] unless hash.present? + + #todo: need to create a function to create org hash + # OrgSelection::HashToOrgService.to_identifiers(hash: hash) + end + + # Remove the extraneous Org Selector hidden fields so that they don't get + # passed on to any save methods + def remove_org_selection_params(params_in:) + params_in.delete(:org_id) + params_in.delete(:org_name) + params_in.delete(:org_sources) + params_in.delete(:org_crosswalk) + params_in + end + + # Just does a JSON parse of the org_id hash + def org_hash_from_params(params_in:) + JSON.parse(params_in[:org_id]) # .with_indifferent_access + rescue JSON::ParserError => e + Rails.logger.error "Unable to parse Org Selection JSON: #{e.message}" + Rails.logger.error params_in.inspect + {} + end + + # Saves the org if its a new record + def create_org(org:, params_in:) + return org unless org.present? && org.new_record? + + # Save the Org before attaching identifiers + org.save + identifiers_from_params(params_in: params_in).each do |identifier| + next unless identifier.value.present? + + identifier.identifiable = org + identifier.save + end + org.reload + end + end + # rubocop:enable Metrics/BlockLength +end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 53492a3f9e..09dda0e80a 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -2,7 +2,8 @@ # Controller that handles user account creation and changes from the edit profile page class RegistrationsController < Devise::RegistrationsController - include OrgSelectable + # include OrgSelectable + include OrgCreationWithOrion def edit @user = current_user From f210da4375f18c8c9bd7140a7ae03e38fd75bbfb Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 23 Jul 2025 17:01:08 +0100 Subject: [PATCH 22/42] Add new concern to enable org creation if org does not exist --- app/controllers/api/org_domain_controller.rb | 24 ++++++++++++------- .../concerns/org_creation_with_orion.rb | 9 +++---- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index bfa584ac7f..198383f3be 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -5,18 +5,20 @@ module Api class OrgDomainController < ApplicationController # PUTS /api/orgs-by-domain with parameter email. - #TBD: Change these Rubocop Cops + # TBD: Change these Rubocop Cops # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def index email_param = search_params[:email] email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') render json: [], status: :ok if email_domain.blank? - # Filter orgs by domain if domain parameter is provided + # check if org exists already using domain provided org_results = OrgDomain.search_with_org_info(email_domain) result = org_results.map { |record| + org_id_new_format = {id: record.id, name: record.org_name}.to_json + { - id: record.id, + id: org_id_new_format, org_name: record.org_name, domain: record.domain, } @@ -28,7 +30,7 @@ def index return end - #---------Orion service code start + # if org doesn't exist already call Orion API by passing domain begin full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) puts "full_org_json: #{full_org_json}" @@ -36,8 +38,9 @@ def index unless full_org_json&.key?('orgs') puts 'Invalid response or no orgs key found' other_org = Org.find_other_org + org_id_new_format = {id: other_org.id, name: other_org.org_name}.to_json result = [{ - id: other_org.id, + id: org_id_new_format, org_name: other_org.name, domain: '' }] @@ -45,11 +48,13 @@ def index return end - # Extract the value + # Extract the values from API result result = full_org_json['orgs'].map do |org| title = org['names'].find { |n| n['types'].include?('ror_display') } + # ror_id_formatted = org['id'].split('/').last + org_id_new_format = {name: title['value']}.to_json { - id: org['id'].split('/').last, + id: org_id_new_format, org_name: title ? title['value'] : 'Name not found', domain: '', } @@ -58,11 +63,12 @@ def index result = [] end - # Fallback if still no results + # if no org exists - assign to org called 'Other' if result.blank? other_org = Org.find_other_org + org_id_new_format = {id: other_org.id, name: other_org.org_name}.to_json result = [{ - id: other_org.id, + id: org_id_new_format, org_name: other_org.name, domain: '' }] diff --git a/app/controllers/concerns/org_creation_with_orion.rb b/app/controllers/concerns/org_creation_with_orion.rb index 3813e6588b..8d3214a714 100644 --- a/app/controllers/concerns/org_creation_with_orion.rb +++ b/app/controllers/concerns/org_creation_with_orion.rb @@ -73,10 +73,8 @@ def org_from_params(params_in:, hash = org_hash_from_params(params_in: params_in) return nil unless hash.present? - #todo: need to create a function to create org hash - org = {"name": "University of Stanford (uofa.edu)"} - # org = OrgSelection::HashToOrgService.to_org(hash: hash, - # allow_create: allow_create) + org = OrgSelection::HashToOrgService.to_org(hash: hash, + allow_create: allow_create) allow_create ? create_org(org: org, params_in: params_in) : org end @@ -89,8 +87,7 @@ def identifiers_from_params(params_in:) hash = org_hash_from_params(params_in: params_in) return [] unless hash.present? - #todo: need to create a function to create org hash - # OrgSelection::HashToOrgService.to_identifiers(hash: hash) + OrgSelection::HashToOrgService.to_identifiers(hash: hash) end # Remove the extraneous Org Selector hidden fields so that they don't get From be06713472e861cc5c38179ca4259edfdf40fc7c Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 24 Jul 2025 16:05:15 +0100 Subject: [PATCH 23/42] Add orion api domain and fix bug for Other org --- app/controllers/api/org_domain_controller.rb | 2 +- app/services/external_apis/orion_service.rb | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/api/org_domain_controller.rb index 198383f3be..2dd7db4c46 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/api/org_domain_controller.rb @@ -38,7 +38,7 @@ def index unless full_org_json&.key?('orgs') puts 'Invalid response or no orgs key found' other_org = Org.find_other_org - org_id_new_format = {id: other_org.id, name: other_org.org_name}.to_json + org_id_new_format = {id: other_org.id, name: other_org.name}.to_json result = [{ id: org_id_new_format, org_name: other_org.name, diff --git a/app/services/external_apis/orion_service.rb b/app/services/external_apis/orion_service.rb index e4fdc3eec8..7e4d27e3fc 100644 --- a/app/services/external_apis/orion_service.rb +++ b/app/services/external_apis/orion_service.rb @@ -6,7 +6,7 @@ module ExternalApis class OrionService - ORION_URL = "http://74.220.18.40:8080/submit" + ORION_URL = "http://srv01.screco.org:8080/submit" def self.search_by_ror_id(ror_id) return { error: 'Missing ROR ID' } if ror_id.blank? @@ -38,7 +38,8 @@ def self.post_to_orion(payload) payload.to_json, { "Content-Type" => "application/json" } ) - + + # puts ">>>>>>>:#{response.body}" JSON.parse(response.body) rescue JSON::ParserError { error: 'Invalid response from Orion' } From 782d7daf28763853d2ad96124f04baa9cd6ea517 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 29 Jul 2025 10:40:16 +0100 Subject: [PATCH 24/42] Updated org_creation_with_orion.rb to add a domain using the user's email if the domain does not exist in the org_domains table. Further, work required to deal with new domains for same org. --- .../concerns/org_creation_with_orion.rb | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/app/controllers/concerns/org_creation_with_orion.rb b/app/controllers/concerns/org_creation_with_orion.rb index 8d3214a714..79feb6481e 100644 --- a/app/controllers/concerns/org_creation_with_orion.rb +++ b/app/controllers/concerns/org_creation_with_orion.rb @@ -58,7 +58,6 @@ module OrgCreationWithOrion # rubocop:disable Metrics/BlockLength included do - private # Converts the incoming params_into an Org by either locating it @@ -73,9 +72,12 @@ def org_from_params(params_in:, hash = org_hash_from_params(params_in: params_in) return nil unless hash.present? - org = OrgSelection::HashToOrgService.to_org(hash: hash, - allow_create: allow_create) - allow_create ? create_org(org: org, params_in: params_in) : org + org_from_hash = OrgSelection::HashToOrgService.to_org(hash: hash, + allow_create: allow_create) + org_does_not_exist = Org.where(id: org_from_hash.id).empty? + org = allow_create ? create_org(org: org_from_hash, params_in: params_in) : org_from_hash + create_org_domain(org: org, params_in: params_in) if org_does_not_exist + org end # Converts the incoming params_into an array of Identifiers @@ -124,5 +126,21 @@ def create_org(org:, params_in:) org.reload end end - # rubocop:enable Metrics/BlockLength + + # Creates an OrgDomain record if it does not already exist + # rubocop:disable Metrics/AbcSize + def create_org_domain(org:, params_in:) + return unless org.present? && params_in[:email].present? + + domain = params_in[:email].split("@", 2)[1].downcase.strip + puts domain + return if domain.blank? + return if org.org_domains.exists?(domain: domain) + + org.org_domains.create(domain: domain) + rescue StandardError => e + Rails.logger.error "Error creating OrgDomain for #{org.name} with domain #{domain}: #{e.message}" + end + + # rubocop:enable Metrics/AbcSize, Metrics/BlockLength end From c5dbd14b3b3cb6ecdc72997472331dc5a4a27e38 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Wed, 30 Jul 2025 12:24:10 +0100 Subject: [PATCH 25/42] Removed the previous commit's creation of org domains if they don't exist. As this could restrict the orgs that share the same domain from being chosen. --- .../concerns/org_creation_with_orion.rb | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/controllers/concerns/org_creation_with_orion.rb b/app/controllers/concerns/org_creation_with_orion.rb index 79feb6481e..a7390a2eae 100644 --- a/app/controllers/concerns/org_creation_with_orion.rb +++ b/app/controllers/concerns/org_creation_with_orion.rb @@ -74,9 +74,9 @@ def org_from_params(params_in:, org_from_hash = OrgSelection::HashToOrgService.to_org(hash: hash, allow_create: allow_create) - org_does_not_exist = Org.where(id: org_from_hash.id).empty? org = allow_create ? create_org(org: org_from_hash, params_in: params_in) : org_from_hash - create_org_domain(org: org, params_in: params_in) if org_does_not_exist + # No longer creating domain as it could have issues with cases where multiple ROR orgs have same domain. + # create_org_domain_if_absent(org: org, params_in: params_in) # No longer creating domain as it could have issues with case org end @@ -129,18 +129,18 @@ def create_org(org:, params_in:) # Creates an OrgDomain record if it does not already exist # rubocop:disable Metrics/AbcSize - def create_org_domain(org:, params_in:) - return unless org.present? && params_in[:email].present? - - domain = params_in[:email].split("@", 2)[1].downcase.strip - puts domain - return if domain.blank? - return if org.org_domains.exists?(domain: domain) - - org.org_domains.create(domain: domain) - rescue StandardError => e - Rails.logger.error "Error creating OrgDomain for #{org.name} with domain #{domain}: #{e.message}" - end + # def create_org_domain_if_absent(org:, params_in:) + # return unless org.present? && params_in[:email].present? + + # domain = params_in[:email].split('@', 2)[1].downcase.strip + # puts domain + # return if domain.blank? + # return if org.org_domains.exists?(domain: domain) + + # org.org_domains.create(domain: domain) + # rescue StandardError => e + # Rails.logger.error "Error creating OrgDomain for #{org.name} with domain #{domain}: #{e.message}" + # end # rubocop:enable Metrics/AbcSize, Metrics/BlockLength end From 298cdaf2b5971511291a5a7faf97ee8e4e34c40e Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 14 Aug 2025 12:07:49 +0100 Subject: [PATCH 26/42] Move org_domain controller out of api namespace --- app/controllers/{api => }/org_domain_controller.rb | 3 +-- app/javascript/src/shared/createAccountForm.js | 2 +- config/routes.rb | 4 +++- 3 files changed, 5 insertions(+), 4 deletions(-) rename app/controllers/{api => }/org_domain_controller.rb (99%) diff --git a/app/controllers/api/org_domain_controller.rb b/app/controllers/org_domain_controller.rb similarity index 99% rename from app/controllers/api/org_domain_controller.rb rename to app/controllers/org_domain_controller.rb index 2dd7db4c46..58f12a24dc 100644 --- a/app/controllers/api/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -module Api # Controller for API routes that return orgs by domain. class OrgDomainController < ApplicationController @@ -85,4 +84,4 @@ def search_params params.permit(:email, :format, :org_domain) end end -end + diff --git a/app/javascript/src/shared/createAccountForm.js b/app/javascript/src/shared/createAccountForm.js index 3df5dfe5a3..1b57f115c3 100644 --- a/app/javascript/src/shared/createAccountForm.js +++ b/app/javascript/src/shared/createAccountForm.js @@ -64,7 +64,7 @@ $(() => { }; // Use Fetch API with POST configuration included in requestOptions - fetch('/api/orgs-by-domain', requestOptions) + fetch('/orgs-by-domain', requestOptions) .then(response => response.json()) .then(data => { populateOrgSelect(data); diff --git a/config/routes.rb b/config/routes.rb index 38803cc150..a013675cd0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -204,9 +204,11 @@ resources :templates, only: [:index] end - post 'orgs-by-domain', to: 'org_domain#index' end + post 'orgs-by-domain', to: 'org_domain#index' + + namespace :paginable do resources :orgs, only: [] do get 'index/:page', action: :index, on: :collection, as: :index From e614aeb6ac91e0e666036e7744b4b771bb6ff07a Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Thu, 14 Aug 2025 16:02:56 +0100 Subject: [PATCH 27/42] Add new page for adding and editing org domains --- app/controllers/org_domain_controller.rb | 183 ++++++++++++++--------- app/views/layouts/_branding.html.erb | 5 + app/views/org_domain/edit.html.erb | 13 ++ app/views/org_domain/new.html.erb | 13 ++ app/views/org_domain/show.html.erb | 32 ++++ config/routes.rb | 2 + 6 files changed, 176 insertions(+), 72 deletions(-) create mode 100644 app/views/org_domain/edit.html.erb create mode 100644 app/views/org_domain/new.html.erb create mode 100644 app/views/org_domain/show.html.erb diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index 58f12a24dc..30005a0e09 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -1,87 +1,126 @@ # frozen_string_literal: true - # Controller for API routes that return orgs by domain. - class OrgDomainController < ApplicationController - - # PUTS /api/orgs-by-domain with parameter email. - # TBD: Change these Rubocop Cops - # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity - def index - email_param = search_params[:email] - email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') - render json: [], status: :ok if email_domain.blank? - - # check if org exists already using domain provided - org_results = OrgDomain.search_with_org_info(email_domain) - result = org_results.map { |record| - org_id_new_format = {id: record.id, name: record.org_name}.to_json +# Controller for API routes that return orgs by domain. +class OrgDomainController < ApplicationController - { - id: org_id_new_format, - org_name: record.org_name, - domain: record.domain, - } + # PUTS /orgs-by-domain with parameter email. + # TBD: Change these Rubocop Cops + # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity + def index + email_param = search_params[:email] + email_domain = email_param.split('@').last if email_param.present? && email_param.include?('@') + render json: [], status: :ok if email_domain.blank? + + # check if org exists already using domain provided + org_results = OrgDomain.search_with_org_info(email_domain) + result = org_results.map { |record| + org_id_new_format = {id: record.id, name: record.org_name}.to_json + + { + id: org_id_new_format, + org_name: record.org_name, + domain: record.domain, } + } + + unless result.empty? + puts "result: #{result}" + render json: result, status: :ok + return + end + + # if org doesn't exist already call Orion API by passing domain + begin + full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) + puts "full_org_json: #{full_org_json}" - unless result.empty? - puts "result: #{result}" + unless full_org_json&.key?('orgs') + puts 'Invalid response or no orgs key found' + other_org = Org.find_other_org + org_id_new_format = {id: other_org.id, name: other_org.name}.to_json + result = [{ + id: org_id_new_format, + org_name: other_org.name, + domain: '' + }] render json: result, status: :ok return end - - # if org doesn't exist already call Orion API by passing domain - begin - full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) - puts "full_org_json: #{full_org_json}" - - unless full_org_json&.key?('orgs') - puts 'Invalid response or no orgs key found' - other_org = Org.find_other_org - org_id_new_format = {id: other_org.id, name: other_org.name}.to_json - result = [{ - id: org_id_new_format, - org_name: other_org.name, - domain: '' - }] - render json: result, status: :ok - return - end - - # Extract the values from API result - result = full_org_json['orgs'].map do |org| - title = org['names'].find { |n| n['types'].include?('ror_display') } - # ror_id_formatted = org['id'].split('/').last - org_id_new_format = {name: title['value']}.to_json - { - id: org_id_new_format, - org_name: title ? title['value'] : 'Name not found', - domain: '', - } - rescue => e - puts "Failed request: #{e.message}" - result = [] - end - - # if no org exists - assign to org called 'Other' - if result.blank? - other_org = Org.find_other_org - org_id_new_format = {id: other_org.id, name: other_org.org_name}.to_json - result = [{ - id: org_id_new_format, - org_name: other_org.name, - domain: '' - }] - end + + # Extract the values from API result + result = full_org_json['orgs'].map do |org| + title = org['names'].find { |n| n['types'].include?('ror_display') } + # ror_id_formatted = org['id'].split('/').last + org_id_new_format = {name: title['value']}.to_json + { + id: org_id_new_format, + org_name: title ? title['value'] : 'Name not found', + domain: '', + } + rescue => e + puts "Failed request: #{e.message}" + result = [] end - render json: result, status: :ok + + # if no org exists - assign to org called 'Other' + if result.blank? + other_org = Org.find_other_org + org_id_new_format = {id: other_org.id, name: other_org.org_name}.to_json + result = [{ + id: org_id_new_format, + org_name: other_org.name, + domain: '' + }] + end + end + render json: result, status: :ok + end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity + + def show + @org_domains = OrgDomain.where(org_id: current_user.org_id).order(created_at: :desc) + end + + def new + @org_domain = OrgDomain.new + end + + def create + @org_domain = OrgDomain.new(org_domain_params) + @org_domain.org_id = current_user.org_id + + if @org_domain.save + redirect_to org_domain_show_path, notice: "Domain created successfully." + else + render :new end - # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity + end - private + def edit + @org_domain = OrgDomain.find(params[:id]) + redirect_to org_domain_show_path, alert: "Unauthorized" unless @org_domain.org_id == current_user.org_id + end - # Using Strong Parameters ensure only domain is permitted - def search_params - params.permit(:email, :format, :org_domain) + def update + @org_domain = OrgDomain.find(params[:id]) + if @org_domain.org_id != current_user.org_id + redirect_to org_domain_show_path, alert: "Unauthorized" + elsif @org_domain.update(org_domain_params) + redirect_to org_domain_show_path, notice: "Domain updated successfully." + else + render :edit end end + private + + # Using Strong Parameters ensure only domain is permitted + def search_params + params.permit(:email, :format, :org_domain) + end + + def org_domain_params + params.require(:org_domain).permit(:domain) + end +end + diff --git a/app/views/layouts/_branding.html.erb b/app/views/layouts/_branding.html.erb index 62ff6493f8..5d494766d7 100644 --- a/app/views/layouts/_branding.html.erb +++ b/app/views/layouts/_branding.html.erb @@ -98,6 +98,11 @@ <% end %> <% end %> + <% if current_user.can_org_admin? %> + + <% end %> <% if current_user.can_grant_permissions? %> <% end %> <% end %> - <% if current_user.can_org_admin? %> + <% if current_user.can_org_admin? && !current_user.can_super_admin? %> From 0eb8ccd38699564dec553d75e0ac30bf874b9481 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Tue, 26 Aug 2025 15:25:36 +0100 Subject: [PATCH 36/42] Add sort by domain for org domains page --- app/controllers/org_domain_controller.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index 0d64804416..31beb66c08 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -75,7 +75,8 @@ def index # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def show - @org_domains = OrgDomain.where(org_id: current_user.org_id).order(created_at: :desc) + @org_domains = OrgDomain.where(org_id: current_user.org_id).order(:domain) + end def new From 32be0a46ea38629217991e22b1c3b9a244dd307c Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Tue, 26 Aug 2025 16:37:22 +0100 Subject: [PATCH 37/42] Change org_type from institution to organisation when a new org is created --- app/services/org_selection/hash_to_org_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/org_selection/hash_to_org_service.rb b/app/services/org_selection/hash_to_org_service.rb index 14ca1263e4..bd5d154f51 100644 --- a/app/services/org_selection/hash_to_org_service.rb +++ b/app/services/org_selection/hash_to_org_service.rb @@ -91,7 +91,7 @@ def initialize_org(hash:) links: links_from_hash(name: hash[:name], website: hash[:url]), language: language_from_hash(hash: hash), target_url: hash[:url], - institution: true, + organisation: true, is_other: false, abbreviation: abbreviation_from_hash(hash: hash) ) From 7ba155a453731843d7b25f3cba26dfb4c14abf1c Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 3 Sep 2025 16:00:58 +0100 Subject: [PATCH 38/42] Add error handling code to create an org called Other if it doesn't exist --- app/controllers/org_domain_controller.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index 31beb66c08..b7032ae67a 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -52,7 +52,7 @@ def index org_name = ror_display_name_json ? ror_display_name_json['value'] : nil puts "org_name: #{org_name}" - # # If org_name is nil, skip this org + # If org_name is nil, skip this org break if org_name.nil? org_id_new_format = { name: org_name }.to_json @@ -157,12 +157,17 @@ def org_domain_params def other_org_json other_org = Org.find_other_org - org_id_new_format = { id: other_org.id, name: other_org.name }.to_json - { - id: org_id_new_format, - org_name: other_org.name, - domain: "", - } + #add if condition here to check if other_org is nil or present + if other_org.present? + org_id_new_format = { id: other_org.id, name: other_org.name }.to_json + else + org_id_new_format = { name: "Other" }.to_json + end + { + id: org_id_new_format, + org_name: other_org ? other_org.name : "Other", + domain: "", + } end end From 872354eaf09e32d6b46829ab0852fee303616414 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 23 Sep 2025 12:45:03 +0100 Subject: [PATCH 39/42] Fix fot case where the OrionService call is such that no data can be extracted in app/controllers/org_domain_controller.rb and so we need to set the result which is nil to an empty array []. --- app/controllers/org_domain_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index b7032ae67a..678ee5e4f6 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -63,10 +63,10 @@ def index } rescue => e puts "Failed request: #{e.message}" - # If the request fails, log the error and return an empty array - result = [] end + # In case result is ni, we need to set it to an empty array + result = [] if result.nil? # Add Other org to end of array. result << other_org_json end From 07a2231cfd92ffa1f7bf4b9bde06dcf7eb472356 Mon Sep 17 00:00:00 2001 From: John Pinto Date: Tue, 7 Oct 2025 13:20:52 +0100 Subject: [PATCH 40/42] Added functionality to org_domain_controller.rb, if no orgs found in first call to the Orion service then we repeat the call to higher level domains until we get to domains with 2 parts, e.g., efe.xyz.abc.com ->... --> abc.com. --- app/controllers/org_domain_controller.rb | 82 +++++++++++++----------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index 678ee5e4f6..ba5de46e09 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -14,7 +14,7 @@ def index # check if org exists already using domain provided org_results = OrgDomain.search_with_org_info(email_domain) result = org_results.map { |record| - org_id_new_format = {id: record.id, name: record.org_name}.to_json + org_id_new_format = { id: record.id, name: record.org_name }.to_json { id: org_id_new_format, @@ -36,6 +36,17 @@ def index full_org_json = ::ExternalApis::OrionService.search_by_domain(email_domain) puts "full_org_json: #{full_org_json}" + # If no orgs found, retry with higher level domain by removing subdomains + split_domain = email_domain.split('.') + + while !full_org_json&.key?('orgs') && split_domain.length > 2 + split_domain.shift + domain_to_search = split_domain.join('.') + puts "Retrying with #{domain_to_search}" + full_org_json = ::ExternalApis::OrionService.search_by_domain(domain_to_search) + puts "Retry full_org_json with #{domain_to_search}: #{full_org_json}" + end + unless full_org_json&.key?('orgs') puts 'Invalid response or no orgs key found' # Add Other org @@ -45,10 +56,10 @@ def index end # Extract the values from API result - result = full_org_json['orgs'].map do |org| + result = full_org_json['orgs'].map do |org| # The ror_display value will be in the language of the country, and should always be present. ror_display_name_json = org['names'].find { |n| n['lang'] && n['types']&.include?('ror_display') } - #puts "ror_display_name_json: #{ror_display_name_json}" + # puts "ror_display_name_json: #{ror_display_name_json}" org_name = ror_display_name_json ? ror_display_name_json['value'] : nil puts "org_name: #{org_name}" @@ -59,9 +70,10 @@ def index { id: org_id_new_format, org_name: org_name, - domain: '', + domain: '' } - rescue => e + + rescue StandardError => e puts "Failed request: #{e.message}" end @@ -72,22 +84,23 @@ def index end render json: result, status: :ok end + # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def show @org_domains = OrgDomain.where(org_id: current_user.org_id).order(:domain) - end def new @org_domain = OrgDomain.new end + # rubocop:disable Metrics/AbcSize def create domain_input = params[:org_domain][:domain].to_s.downcase.gsub(/\s+/, '') if domain_input.blank? - flash.now[:alert] = "Domain can't be blank." + flash.now[:alert] = 'Domain cannot be blank.' @org_domain = OrgDomain.new render :new and return end @@ -96,53 +109,55 @@ def create @org_domain.org_id = current_user.org_id if @org_domain.save - redirect_to org_domain_show_path, notice: "Domain created successfully." + redirect_to org_domain_show_path, notice: 'Domain created successfully.' else render :new end end + # rubocop:enable Metrics/AbcSize def edit @org_domain = OrgDomain.find(params[:id]) - redirect_to org_domain_show_path, alert: "Unauthorized" unless @org_domain.org_id == current_user.org_id + redirect_to org_domain_show_path, alert: 'Unauthorized' unless @org_domain.org_id == current_user.org_id end + # rubocop:disable Metrics/AbcSize def update @org_domain = OrgDomain.find(params[:id]) - - if @org_domain.org_id != current_user.org_id - redirect_to org_domain_show_path, alert: "Unauthorized" - else + + if @org_domain.org_id == current_user.org_id domain_input = params[:org_domain][:domain].to_s.downcase.gsub(/\s+/, '') - + if domain_input.blank? - flash.now[:alert] = "Domain can't be blank." + flash.now[:alert] = 'Domain cannot be blank.' render :edit and return end - + if @org_domain.update(domain: domain_input) - redirect_to org_domain_show_path, notice: "Domain updated successfully." + redirect_to org_domain_show_path, notice: 'Domain updated successfully.' else render :edit end + else + redirect_to org_domain_show_path, alert: 'Unauthorized' end end - + # rubocop:enable Metrics/AbcSize + def destroy @org_domain = OrgDomain.find(params[:id]) - + if @org_domain.org_id != current_user.org_id - redirect_to org_domain_show_path, alert: "Unauthorized" + redirect_to org_domain_show_path, alert: 'Unauthorized' return end - + if @org_domain.destroy - redirect_to org_domain_show_path, notice: "Domain deleted successfully." + redirect_to org_domain_show_path, notice: 'Domain deleted successfully.' else - redirect_to org_domain_show_path, alert: "Failed to delete domain." + redirect_to org_domain_show_path, alert: 'Failed to delete domain.' end end - private @@ -157,17 +172,12 @@ def org_domain_params def other_org_json other_org = Org.find_other_org - #add if condition here to check if other_org is nil or present - if other_org.present? - org_id_new_format = { id: other_org.id, name: other_org.name }.to_json - else - org_id_new_format = { name: "Other" }.to_json - end - { - id: org_id_new_format, - org_name: other_org ? other_org.name : "Other", - domain: "", - } + # add if condition here to check if other_org is nil or present + org_id_new_format = other_org.present? ? { id: other_org.id, name: other_org.name }.to_json : { name: 'Other' }.to_json + { + id: org_id_new_format, + org_name: other_org ? other_org.name : 'Other', + domain: '' + } end end - From dddc36a9b30fe60dbc2b4fc5df32fb97e12d585d Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 8 Oct 2025 12:12:02 +0100 Subject: [PATCH 41/42] Add super admin access to org domains pages and sync with dmp_core branch --- app/controllers/org_domain_controller.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/org_domain_controller.rb b/app/controllers/org_domain_controller.rb index ba5de46e09..3f61574345 100644 --- a/app/controllers/org_domain_controller.rb +++ b/app/controllers/org_domain_controller.rb @@ -77,7 +77,7 @@ def index puts "Failed request: #{e.message}" end - # In case result is ni, we need to set it to an empty array + # In case result is nil, we need to set it to an empty array result = [] if result.nil? # Add Other org to end of array. result << other_org_json @@ -88,15 +88,18 @@ def index # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity def show + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? @org_domains = OrgDomain.where(org_id: current_user.org_id).order(:domain) end def new + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? @org_domain = OrgDomain.new end # rubocop:disable Metrics/AbcSize def create + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? domain_input = params[:org_domain][:domain].to_s.downcase.gsub(/\s+/, '') if domain_input.blank? @@ -117,12 +120,14 @@ def create # rubocop:enable Metrics/AbcSize def edit + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? @org_domain = OrgDomain.find(params[:id]) redirect_to org_domain_show_path, alert: 'Unauthorized' unless @org_domain.org_id == current_user.org_id end # rubocop:disable Metrics/AbcSize def update + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? @org_domain = OrgDomain.find(params[:id]) if @org_domain.org_id == current_user.org_id @@ -145,6 +150,7 @@ def update # rubocop:enable Metrics/AbcSize def destroy + redirect_to root_path, alert: "You are not authorized to view this page." unless current_user.can_org_admin? || current_user.can_super_admin? @org_domain = OrgDomain.find(params[:id]) if @org_domain.org_id != current_user.org_id From e88bb6ea0d61a0b982d4c02b660a9c7a2694de32 Mon Sep 17 00:00:00 2001 From: gjacob24 Date: Wed, 8 Oct 2025 12:23:00 +0100 Subject: [PATCH 42/42] Add super admin access to org domains in admin menu dropdown --- app/views/layouts/_branding.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_branding.html.erb b/app/views/layouts/_branding.html.erb index 6d2b43ada3..cf7d1cde97 100644 --- a/app/views/layouts/_branding.html.erb +++ b/app/views/layouts/_branding.html.erb @@ -98,11 +98,11 @@ <% end %> <% end %> - <% if current_user.can_org_admin? && !current_user.can_super_admin? %> + <% if current_user.can_org_admin? || current_user.can_super_admin? %> - <% end %> + <% end %> <% if current_user.can_grant_permissions? %>