diff --git a/Gemfile b/Gemfile index ea641322a5..1454fe8f88 100644 --- a/Gemfile +++ b/Gemfile @@ -56,6 +56,7 @@ gem "languagetool-widget"#, path: "../../noesya/languagetool-widget" gem "leaflet-rails" gem "libretranslate"#, path: "../libretranslate" gem "lucide-rails" +gem "minitest", "< 6" # TODO Seb : Upgrade en Rails 8.1.2 gem "metainspector", git: "https://github.com/jaimeiniesta/metainspector.git" gem "octokit" gem "omniauth-saml", "~> 2" diff --git a/Gemfile.lock b/Gemfile.lock index 90aadd4085..c3f818b422 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -167,14 +167,14 @@ GEM aws-eventstream (~> 1, >= 1.0.2) babosa (2.1.0) base64 (0.3.0) - bcrypt (3.1.22) + bcrypt (3.1.21) better_errors (2.10.1) erubi (>= 1.0.0) rack (>= 0.9.0) rouge (>= 1.0.0) - bigdecimal (4.1.2) + bigdecimal (4.0.1) bindex (0.8.1) - binding_of_caller (2.0.0) + binding_of_caller (1.0.1) debug_inspector (>= 1.2.0) bootsnap (1.24.6) msgpack (~> 1.2) @@ -189,7 +189,7 @@ GEM addressable (~> 2.3, >= 2.3.0) json (~> 2.1, >= 2.1.0) typhoeus (~> 1.0, >= 1.0.1) - bugsnag (6.30.0) + bugsnag (6.29.0) concurrent-ruby (~> 1.0) builder (3.3.0) byebug (13.0.0) @@ -254,14 +254,14 @@ GEM rails-html-sanitizer date (3.5.1) debug_inspector (1.2.0) - devise (5.0.4) + devise (4.9.4) bcrypt (~> 3.0) orm_adapter (~> 0.1) - railties (>= 7.0) + railties (>= 4.1.0) responders warden (~> 1.2.3) - devise-i18n (1.16.0) - devise (>= 5.0.0) + devise-i18n (1.15.0) + devise (>= 4.9.0) rails-i18n diff-lcs (1.6.2) domain_name (0.6.20240107) @@ -273,10 +273,9 @@ GEM erubi (1.13.1) et-orbi (1.4.0) tzinfo - ethon (0.18.0) + ethon (0.15.0) ffi (>= 1.15.0) - logger - execjs (2.10.1) + execjs (2.10.0) faceted_search (3.6.4) font-awesome-sass rails (>= 5.2.0) @@ -294,7 +293,7 @@ GEM faraday-gzip (3.1.0) faraday (>= 2.0, < 3) zlib (~> 3.0) - faraday-http-cache (2.7.0) + faraday-http-cache (2.6.1) faraday (>= 0.8) faraday-multipart (1.2.0) multipart-post (~> 2.0) @@ -302,10 +301,10 @@ GEM net-http (~> 0.5) faraday-retry (2.4.0) faraday (~> 2.0) - fastimage (2.4.1) - ffi (1.17.4-arm64-darwin) - ffi (1.17.4-x86_64-darwin) - ffi (1.17.4-x86_64-linux-gnu) + fastimage (2.4.0) + ffi (1.17.3-arm64-darwin) + ffi (1.17.3-x86_64-darwin) + ffi (1.17.3-x86_64-linux-gnu) figaro (1.3.0) thor (>= 0.14.0, < 2) font-awesome-sass (6.7.2) @@ -352,8 +351,8 @@ GEM rake (~> 13.3) google-protobuf (4.35.1-x86_64-linux-gnu) bigdecimal - rake (~> 13.3) - hal_openscience (1.0.0) + rake (>= 13) + hal_openscience (0.1.0) json net-http open-uri @@ -361,7 +360,7 @@ GEM hashie (5.1.0) logger htmlentities (4.4.2) - http-cookie (1.1.6) + http-cookie (1.1.0) domain_name (~> 0.5) httparty (0.24.2) csv @@ -380,12 +379,11 @@ GEM invisible_captcha (2.3.0) rails (>= 5.2) io-console (0.8.2) - irb (1.18.0) + irb (1.16.0) pp (>= 0.6.0) - prism (>= 1.3.0) rdoc (>= 4.0.0) reline (>= 0.4.2) - jbuilder (2.15.1) + jbuilder (2.14.1) actionview (>= 7.0.0) activesupport (>= 7.0.0) jmespath (1.6.2) @@ -401,7 +399,7 @@ GEM json-schema (6.2.0) addressable (~> 2.8) bigdecimal (>= 3.1, < 5) - jwt (3.2.0) + jwt (3.1.2) base64 kamifusen (1.13.2) image_processing @@ -435,7 +433,7 @@ GEM loofah (2.25.2) crass (~> 1.0.2) nokogiri (>= 1.12.0) - lucide-rails (0.7.4) + lucide-rails (0.7.3) railties (>= 4.1.0) mail (2.9.1) logger @@ -443,7 +441,7 @@ GEM net-imap net-pop net-smtp - marcel (1.2.1) + marcel (1.1.0) matrix (0.4.3) mini_mime (1.1.5) minitest (6.0.6) @@ -506,7 +504,7 @@ GEM logger rack (>= 2.2.3) rack-protection - omniauth-saml (2.2.5) + omniauth-saml (2.2.4) omniauth (~> 2.1) ruby-saml (~> 1.18) open-uri (0.5.0) @@ -523,10 +521,10 @@ GEM pg (1.6.3-arm64-darwin) pg (1.6.3-x86_64-darwin) pg (1.6.3-x86_64-linux) - pg_query (6.2.2) + pg_query (6.2.1) google-protobuf (>= 3.25.3) - pghero (3.8.0) - activerecord (>= 7.2) + pghero (3.7.0) + activerecord (>= 7.1) popper_js (2.11.8) pp (0.6.4) prettyprint @@ -544,7 +542,7 @@ GEM base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) - rack-session (2.1.2) + rack-session (2.1.1) base64 (>= 0.1.0) rack (>= 3.0.0) rack-test (2.2.0) @@ -587,7 +585,7 @@ GEM thor (~> 1.0, >= 1.2.2) tsort (>= 0.2) zeitwerk (~> 2.6) - rake (13.4.2) + rake (13.3.1) randexp (0.1.7) rb-fsevent (0.11.2) rb-inotify (0.11.1) @@ -607,7 +605,7 @@ GEM redis-client (>= 0.22.0) redis-client (0.30.1) connection_pool - regexp_parser (2.12.0) + regexp_parser (2.11.3) reline (0.6.3) io-console (~> 0.5) requests (1.0.2) @@ -624,25 +622,24 @@ GEM rubyzip (>= 1.3.0, < 3.0.0) rorvswild (1.12.0) rotp (6.3.0) - rouge (5.0.0) - strscan (~> 3.1) + rouge (4.7.0) rspec-core (3.13.6) rspec-support (~> 3.13.0) rspec-expectations (3.13.5) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-mocks (3.13.8) + rspec-mocks (3.13.7) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.13.0) - rspec-rails (8.0.4) + rspec-rails (8.0.2) actionpack (>= 7.2) activesupport (>= 7.2) railties (>= 7.2) - rspec-core (>= 3.13.0, < 5.0.0) - rspec-expectations (>= 3.13.0, < 5.0.0) - rspec-mocks (>= 3.13.0, < 5.0.0) - rspec-support (>= 3.13.0, < 5.0.0) - rspec-support (3.13.7) + rspec-core (~> 3.13) + rspec-expectations (~> 3.13) + rspec-mocks (~> 3.13) + rspec-support (~> 3.13) + rspec-support (3.13.6) rswag (2.17.0) rswag-api (= 2.17.0) rswag-specs (= 2.17.0) @@ -696,7 +693,7 @@ GEM open-uri rails simple_form - simple_form_password_with_hints (0.0.12) + simple_form_password_with_hints (0.0.10) rails simple_form simple_po_parser (1.1.6) @@ -715,7 +712,6 @@ GEM activesupport (>= 6.1) sprockets (>= 3.0.0) stringio (3.2.0) - strscan (3.1.8) sugar-high (0.7.3) sweetloader (0.1.6) activesupport (>= 3.0.1) @@ -726,10 +722,10 @@ GEM tilt (2.8.0) time (0.4.2) date - timeout (0.6.1) + timeout (0.6.0) tsort (0.2.0) - typhoeus (1.6.0) - ethon (>= 0.18.0) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) tzinfo-data (1.2026.3) @@ -750,15 +746,16 @@ GEM oauth (>= 0.4.3) warden (1.2.9) rack (>= 2.0.9) - web-console (4.3.0) - actionview (>= 8.0.0) + web-console (4.2.1) + actionview (>= 6.0.0) + activemodel (>= 6.0.0) bindex (>= 0.4.0) - railties (>= 8.0.0) + railties (>= 6.0.0) webdrivers (5.3.1) nokogiri (~> 1.6) rubyzip (>= 1.3.0) selenium-webdriver (~> 4.0, < 4.11) - webmock (3.26.2) + webmock (3.26.1) addressable (>= 2.8.0) crack (>= 0.3.2) hashdiff (>= 0.4.0, < 2.0.0) @@ -769,8 +766,8 @@ GEM websocket-extensions (0.1.5) xpath (3.2.0) nokogiri (~> 1.8) - zeitwerk (2.8.2) - zlib (3.2.3) + zeitwerk (2.7.4) + zlib (3.2.2) PLATFORMS arm64-darwin-23 @@ -837,6 +834,7 @@ DEPENDENCIES listen (~> 3.10) lucide-rails metainspector! + minitest (< 6) mutex_m (~> 0.3.0) octokit omniauth-saml (~> 2) diff --git a/app/models/university/organization.rb b/app/models/university/organization.rb index 0e83b35498..7730e0aefc 100644 --- a/app/models/university/organization.rb +++ b/app/models/university/organization.rb @@ -9,6 +9,9 @@ # country :string # deleted_at :datetime # email :string +# is_laboratory :boolean default(FALSE) +# is_location :boolean default(FALSE) +# is_school :boolean default(FALSE) # kind :integer default(10) # latitude :float # longitude :float diff --git a/app/models/university/organization/localization/laboratory.rb b/app/models/university/organization/localization/laboratory.rb new file mode 100644 index 0000000000..0b327e0fdf --- /dev/null +++ b/app/models/university/organization/localization/laboratory.rb @@ -0,0 +1,72 @@ +# == Schema Information +# +# Table name: university_organization_localizations +# +# id :uuid not null, primary key +# address_additional :string +# address_name :string +# deleted_at :datetime +# featured_image_alt :string +# featured_image_credit :text +# linkedin :string +# long_name :string +# mastodon :string +# meta_description :text +# migration_identifier :string +# name :string +# published :boolean default(TRUE) +# published_at :datetime +# slug :string +# summary :text +# text :text +# twitter :string +# url :string +# created_at :datetime not null +# updated_at :datetime not null +# about_id :uuid uniquely indexed => [language_id], indexed +# language_id :uuid uniquely indexed => [about_id], indexed +# university_id :uuid indexed +# +# Indexes +# +# idx_on_about_id_language_id_eb921fd47b (about_id,language_id) UNIQUE +# index_university_organization_localizations_on_about_id (about_id) +# index_university_organization_localizations_on_language_id (language_id) +# index_university_organization_localizations_on_university_id (university_id) +# +# Foreign Keys +# +# fk_rails_19fb4f0718 (about_id => university_organizations.id) +# fk_rails_4b46ee9073 (language_id => languages.id) +# fk_rails_ba221edb00 (university_id => universities.id) +# +class University::Organization::Localization::Laboratory < University::Organization::Localization + def self.polymorphic_name + 'University::Organization::Localization::Laboratory' + end + + def git_path_relative + "laboratories/#{slug}/_index.html" + end + + def template_static + "admin/university/organizations/laboratories/static" + end + + def dependencies + [ + organization_l10n, + organization + ] + end + + def references + # TODO + end + + def static_localization_key + # so we don't mess with the University::Organization::Localization static_localization_key + "#{about.class.polymorphic_name.parameterize}-laboratory-#{self.about_id}" + end + +end diff --git a/app/models/university/organization/localization/location.rb b/app/models/university/organization/localization/location.rb new file mode 100644 index 0000000000..af3c248fa2 --- /dev/null +++ b/app/models/university/organization/localization/location.rb @@ -0,0 +1,72 @@ +# == Schema Information +# +# Table name: university_organization_localizations +# +# id :uuid not null, primary key +# address_additional :string +# address_name :string +# deleted_at :datetime +# featured_image_alt :string +# featured_image_credit :text +# linkedin :string +# long_name :string +# mastodon :string +# meta_description :text +# migration_identifier :string +# name :string +# published :boolean default(TRUE) +# published_at :datetime +# slug :string +# summary :text +# text :text +# twitter :string +# url :string +# created_at :datetime not null +# updated_at :datetime not null +# about_id :uuid uniquely indexed => [language_id], indexed +# language_id :uuid uniquely indexed => [about_id], indexed +# university_id :uuid indexed +# +# Indexes +# +# idx_on_about_id_language_id_eb921fd47b (about_id,language_id) UNIQUE +# index_university_organization_localizations_on_about_id (about_id) +# index_university_organization_localizations_on_language_id (language_id) +# index_university_organization_localizations_on_university_id (university_id) +# +# Foreign Keys +# +# fk_rails_19fb4f0718 (about_id => university_organizations.id) +# fk_rails_4b46ee9073 (language_id => languages.id) +# fk_rails_ba221edb00 (university_id => universities.id) +# +class University::Organization::Localization::Location < University::Organization::Localization + def self.polymorphic_name + 'University::Organization::Localization::Location' + end + + def git_path_relative + "locations/#{slug}/_index.html" + end + + def template_static + "admin/university/organizations/locations/static" + end + + def dependencies + [ + organization_l10n, + organization + ] + end + + def references + # TODO + end + + def static_localization_key + # so we don't mess with the University::Organization::Localization static_localization_key + "#{about.class.polymorphic_name.parameterize}-location-#{self.about_id}" + end + +end diff --git a/app/models/university/organization/localization/school.rb b/app/models/university/organization/localization/school.rb new file mode 100644 index 0000000000..6e96a9bbae --- /dev/null +++ b/app/models/university/organization/localization/school.rb @@ -0,0 +1,72 @@ +# == Schema Information +# +# Table name: university_organization_localizations +# +# id :uuid not null, primary key +# address_additional :string +# address_name :string +# deleted_at :datetime +# featured_image_alt :string +# featured_image_credit :text +# linkedin :string +# long_name :string +# mastodon :string +# meta_description :text +# migration_identifier :string +# name :string +# published :boolean default(TRUE) +# published_at :datetime +# slug :string +# summary :text +# text :text +# twitter :string +# url :string +# created_at :datetime not null +# updated_at :datetime not null +# about_id :uuid uniquely indexed => [language_id], indexed +# language_id :uuid uniquely indexed => [about_id], indexed +# university_id :uuid indexed +# +# Indexes +# +# idx_on_about_id_language_id_eb921fd47b (about_id,language_id) UNIQUE +# index_university_organization_localizations_on_about_id (about_id) +# index_university_organization_localizations_on_language_id (language_id) +# index_university_organization_localizations_on_university_id (university_id) +# +# Foreign Keys +# +# fk_rails_19fb4f0718 (about_id => university_organizations.id) +# fk_rails_4b46ee9073 (language_id => languages.id) +# fk_rails_ba221edb00 (university_id => universities.id) +# +class University::Organization::Localization::School < University::Organization::Localization + def self.polymorphic_name + 'University::Organization::Localization::School' + end + + def git_path_relative + "schools/#{slug}/_index.html" + end + + def template_static + "admin/university/organizations/schools/static" + end + + def dependencies + [ + organization_l10n, + organization + ] + end + + def references + # TODO + end + + def static_localization_key + # so we don't mess with the University::Organization::Localization static_localization_key + "#{about.class.polymorphic_name.parameterize}-school-#{self.about_id}" + end + +end diff --git a/app/models/university/organization/with_facets.rb b/app/models/university/organization/with_facets.rb new file mode 100644 index 0000000000..fc71b00310 --- /dev/null +++ b/app/models/university/organization/with_facets.rb @@ -0,0 +1,17 @@ +module University::Organization::WithFacets + extend ActiveSupport::Concern + + included do + LIST_OF_FACETS = [ + :location, + :school, + :laboratory + ].freeze + + scope :locations, -> { where(is_location: true) } + scope :schools, -> { where(is_school: true) } + scope :laboratories, -> { where(is_laboratory: true) } + # Filters + scope :for_facet, -> (facet, language = nil) { where("is_#{facet}": true) } + end +end \ No newline at end of file diff --git a/app/models/university/person/with_facets.rb b/app/models/university/person/with_facets.rb index d73e7585fe..4d93b40ba4 100644 --- a/app/models/university/person/with_facets.rb +++ b/app/models/university/person/with_facets.rb @@ -15,7 +15,8 @@ module University::Person::WithFacets scope :teachers, -> { where(is_teacher: true) } scope :researchers, -> { where(is_researcher: true) } scope :alumni, -> { where(is_alumnus: true) } - scope :with_habilitation, -> { where(habilitation: true) } + + # Filters scope :for_facet, -> (facet, language = nil) { where("is_#{facet}": true) } end diff --git a/app/models/university/person/with_realm_research.rb b/app/models/university/person/with_realm_research.rb index e89ddbad31..e21390d942 100644 --- a/app/models/university/person/with_realm_research.rb +++ b/app/models/university/person/with_realm_research.rb @@ -40,6 +40,7 @@ module University::Person::WithRealmResearch through: :research_journal_papers, source: :websites + scope :with_habilitation, -> { where(habilitation: true) } scope :with_hal_identifier, -> { where.not(hal_form_identifier: [nil,'']) } end diff --git a/app/services/migrations/organizations.rb b/app/services/migrations/organizations.rb new file mode 100644 index 0000000000..0b3b288d2f --- /dev/null +++ b/app/services/migrations/organizations.rb @@ -0,0 +1,24 @@ +class Migrations::Organizations + def self.migrate + puts 'Identifying organizations to merge' + University.find_each do |university| + locations = university.locations + schools = university.schools + laboratories = university.laboratories + next if schools.none? && laboratories.none? && locations.none? + puts university + puts '--- locations' + locations.each do |location| + puts "#{location.original_localization} — #{location.id}" + end + puts '--- schools' + schools.each do |school| + puts "#{school.original_localization} — #{school.id}" + end + puts '--- laboratories' + laboratories.each do |laboratory| + puts "#{laboratory.original_localization} — #{laboratory.id}" + end + end + end +end \ No newline at end of file diff --git a/db/migrate/20260119090426_add_booleans_to_university_organization.rb b/db/migrate/20260119090426_add_booleans_to_university_organization.rb new file mode 100644 index 0000000000..0421a7f5ca --- /dev/null +++ b/db/migrate/20260119090426_add_booleans_to_university_organization.rb @@ -0,0 +1,7 @@ +class AddBooleansToUniversityOrganization < ActiveRecord::Migration[8.0] + def change + add_column :university_organizations, :is_school, :boolean, default: false + add_column :university_organizations, :is_location, :boolean, default: false + add_column :university_organizations, :is_laboratory, :boolean, default: false + end +end diff --git a/db/schema.rb b/db/schema.rb index 54dcfc2258..98d11fda9d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -2402,6 +2402,9 @@ t.uuid "created_by_id" t.datetime "deleted_at" t.string "email" + t.boolean "is_laboratory", default: false + t.boolean "is_location", default: false + t.boolean "is_school", default: false t.integer "kind", default: 10 t.float "latitude" t.float "longitude" @@ -2623,6 +2626,20 @@ t.index ["user_id"], name: "index_user_favorites_on_user_id" end + create_table "user_roles", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| + t.datetime "created_at", null: false + t.integer "role", null: false + t.uuid "scope_id" + t.string "scope_type" + t.uuid "university_id", null: false + t.datetime "updated_at", null: false + t.uuid "user_id", null: false + t.index ["scope_type", "scope_id"], name: "index_user_roles_on_scope" + t.index ["university_id"], name: "index_user_roles_on_university_id" + t.index ["user_id", "role", "scope_type", "scope_id"], name: "index_user_roles_uniqueness", unique: true + t.index ["user_id"], name: "index_user_roles_on_user_id" + end + create_table "users", id: :uuid, default: -> { "public.gen_random_uuid()" }, force: :cascade do |t| t.integer "brevo_contact_id" t.datetime "confirmation_sent_at", precision: nil @@ -2654,6 +2671,7 @@ t.string "reset_password_token" t.integer "role", default: 0 t.integer "second_factor_attempts_count", default: 0 + t.boolean "server_admin", default: false t.string "session_token" t.integer "sign_in_count", default: 0, null: false t.datetime "totp_timestamp", precision: nil @@ -3025,6 +3043,8 @@ add_foreign_key "university_role_localizations", "university_roles", column: "about_id" add_foreign_key "university_roles", "universities" add_foreign_key "user_favorites", "users" + add_foreign_key "user_roles", "universities" + add_foreign_key "user_roles", "users" add_foreign_key "users", "languages" add_foreign_key "users", "universities" end