+ <%= (local_assigns[:error_response] ? error_response : @pre_auth.error_response).body[:error_description] %> ++
diff --git a/Gemfile b/Gemfile
index 2a9aa8721d..9736ddfc20 100644
--- a/Gemfile
+++ b/Gemfile
@@ -90,6 +90,8 @@ gem 'devise'
# An invitation strategy for Devise (https://github.com/scambra/devise_invitable)
gem 'devise_invitable'
+gem 'doorkeeper'
+
# A generalized Rack framework for multiple-provider authentication.
# (https://github.com/omniauth/omniauth)
gem 'omniauth'
diff --git a/Gemfile.lock b/Gemfile.lock
index 56db857b5c..f118b37ca9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,723 +1,731 @@
-GEM
- remote: https://rubygems.org/
- specs:
- actioncable (7.1.5.2)
- actionpack (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- nio4r (~> 2.0)
- websocket-driver (>= 0.6.1)
- zeitwerk (~> 2.6)
- actionmailbox (7.1.5.2)
- actionpack (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- mail (>= 2.7.1)
- net-imap
- net-pop
- net-smtp
- actionmailer (7.1.5.2)
- actionpack (= 7.1.5.2)
- actionview (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- mail (~> 2.5, >= 2.5.4)
- net-imap
- net-pop
- net-smtp
- rails-dom-testing (~> 2.2)
- actionpack (7.1.5.2)
- actionview (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- nokogiri (>= 1.8.5)
- racc
- rack (>= 2.2.4)
- rack-session (>= 1.0.1)
- rack-test (>= 0.6.3)
- rails-dom-testing (~> 2.2)
- rails-html-sanitizer (~> 1.6)
- actiontext (7.1.5.2)
- actionpack (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- globalid (>= 0.6.0)
- nokogiri (>= 1.8.5)
- actionview (7.1.5.2)
- activesupport (= 7.1.5.2)
- builder (~> 3.1)
- erubi (~> 1.11)
- rails-dom-testing (~> 2.2)
- rails-html-sanitizer (~> 1.6)
- activejob (7.1.5.2)
- activesupport (= 7.1.5.2)
- globalid (>= 0.3.6)
- activemodel (7.1.5.2)
- activesupport (= 7.1.5.2)
- activerecord (7.1.5.2)
- activemodel (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- timeout (>= 0.4.0)
- activerecord_json_validator (3.1.0)
- activerecord (>= 4.2.0, < 9)
- json_schemer (~> 2.2)
- activestorage (7.1.5.2)
- actionpack (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- marcel (~> 1.0)
- activesupport (7.1.5.2)
- base64
- benchmark (>= 0.3)
- bigdecimal
- concurrent-ruby (~> 1.0, >= 1.0.2)
- connection_pool (>= 2.2.5)
- drb
- i18n (>= 1.6, < 2)
- logger (>= 1.4.2)
- minitest (>= 5.1)
- mutex_m
- securerandom (>= 0.3)
- tzinfo (~> 2.0)
- addressable (2.8.7)
- public_suffix (>= 2.0.2, < 7.0)
- annotate (3.2.0)
- activerecord (>= 3.2, < 8.0)
- rake (>= 10.4, < 14.0)
- annotate_gem (0.0.14)
- bundler (>= 1.1)
- api-pagination (6.0.0)
- ast (2.4.3)
- autoprefixer-rails (10.4.21.0)
- execjs (~> 2)
- base64 (0.3.0)
- bcrypt (3.1.20)
- benchmark (0.4.1)
- better_errors (2.10.1)
- erubi (>= 1.0.0)
- rack (>= 0.9.0)
- rouge (>= 1.0.0)
- bigdecimal (3.3.0)
- bindex (0.8.1)
- binding_of_caller (1.0.1)
- debug_inspector (>= 1.2.0)
- bootsnap (1.18.6)
- msgpack (~> 1.2)
- brakeman (7.1.0)
- racc
- builder (3.3.0)
- bullet (8.0.8)
- activesupport (>= 3.0.0)
- uniform_notifier (~> 1.11)
- bundle-audit (0.1.0)
- bundler-audit
- bundler-audit (0.9.2)
- bundler (>= 1.2.0, < 3)
- thor (~> 1.0)
- byebug (12.0.0)
- capybara (3.40.0)
- addressable
- matrix
- mini_mime (>= 0.1.3)
- nokogiri (~> 1.11)
- rack (>= 1.6.0)
- rack-test (>= 0.6.3)
- regexp_parser (>= 1.5, < 3.0)
- xpath (~> 3.2)
- cgi (0.5.0)
- claide (1.1.0)
- claide-plugins (0.9.2)
- cork
- nap
- open4 (~> 1.3)
- coderay (1.1.3)
- colored2 (3.1.2)
- concurrent-ruby (1.3.5)
- connection_pool (2.5.4)
- contact_us (1.2.0)
- rails (>= 4.2.0)
- cork (0.3.0)
- colored2 (~> 3.1)
- crack (1.0.0)
- bigdecimal
- rexml
- crass (1.0.6)
- cssbundling-rails (1.4.3)
- railties (>= 6.0.0)
- csv (3.3.5)
- danger (9.5.3)
- base64 (~> 0.2)
- claide (~> 1.0)
- claide-plugins (>= 0.9.2)
- colored2 (>= 3.1, < 5)
- cork (~> 0.1)
- faraday (>= 0.9.0, < 3.0)
- faraday-http-cache (~> 2.0)
- git (>= 1.13, < 3.0)
- kramdown (>= 2.5.1, < 3.0)
- kramdown-parser-gfm (~> 1.0)
- octokit (>= 4.0)
- pstore (~> 0.1)
- terminal-table (>= 1, < 5)
- database_cleaner (2.1.0)
- database_cleaner-active_record (>= 2, < 3)
- database_cleaner-active_record (2.2.2)
- activerecord (>= 5.a)
- database_cleaner-core (~> 2.0)
- database_cleaner-core (2.0.1)
- date (3.4.1)
- debug_inspector (1.2.0)
- devise (4.9.4)
- bcrypt (~> 3.0)
- orm_adapter (~> 0.1)
- railties (>= 4.1.0)
- responders
- warden (~> 1.2.3)
- devise_invitable (2.0.11)
- actionmailer (>= 5.0)
- devise (>= 4.6)
- diff-lcs (1.6.2)
- dotenv (3.1.8)
- dotenv-rails (3.1.8)
- dotenv (= 3.1.8)
- railties (>= 6.1)
- dragonfly (1.4.1)
- addressable (~> 2.3)
- multi_json (~> 1.0)
- ostruct (~> 0.6.1)
- rack (>= 1.3)
- dragonfly-s3_data_store (1.3.0)
- dragonfly (~> 1.0)
- fog-aws
- drb (2.2.3)
- erb (4.0.4)
- cgi (>= 0.3.3)
- erubi (1.13.1)
- excon (1.3.0)
- logger
- execjs (2.10.0)
- factory_bot (6.5.5)
- activesupport (>= 6.1.0)
- factory_bot_rails (6.5.1)
- factory_bot (~> 6.5)
- railties (>= 6.1.0)
- faker (3.5.2)
- i18n (>= 1.8.11, < 2)
- faraday (2.14.0)
- faraday-net_http (>= 2.0, < 3.5)
- json
- logger
- faraday-http-cache (2.5.1)
- faraday (>= 0.8)
- faraday-net_http (3.4.1)
- net-http (>= 0.5.0)
- ffi (1.17.2-arm64-darwin)
- ffi (1.17.2-x86_64-linux-gnu)
- flag_shih_tzu (0.3.23)
- fog-aws (3.33.0)
- base64 (>= 0.2, < 0.4)
- fog-core (~> 2.6)
- fog-json (~> 1.1)
- fog-xml (~> 0.1)
- fog-core (2.6.0)
- builder
- excon (~> 1.0)
- formatador (>= 0.2, < 2.0)
- mime-types
- fog-json (1.2.0)
- fog-core
- multi_json (~> 1.10)
- fog-xml (0.1.5)
- fog-core
- nokogiri (>= 1.5.11, < 2.0.0)
- formatador (1.2.1)
- reline
- forwardable (1.3.3)
- fuubar (2.5.1)
- rspec-core (~> 3.0)
- ruby-progressbar (~> 1.4)
- gettext (3.4.9)
- erubi
- locale (>= 2.0.5)
- prime
- racc
- text (>= 1.3.0)
- git (2.3.3)
- activesupport (>= 5.0)
- addressable (~> 2.8)
- process_executer (~> 1.1)
- rchardet (~> 1.8)
- globalid (1.3.0)
- activesupport (>= 6.1)
- guard (2.19.1)
- formatador (>= 0.2.4)
- listen (>= 2.7, < 4.0)
- logger (~> 1.6)
- lumberjack (>= 1.0.12, < 2.0)
- nenv (~> 0.1)
- notiffany (~> 0.0)
- ostruct (~> 0.6)
- pry (>= 0.13.0)
- shellany (~> 0.0)
- thor (>= 0.18.1)
- hana (1.3.7)
- hashdiff (1.2.1)
- hashie (5.0.0)
- highline (3.1.2)
- reline
- htmltoword (1.1.1)
- actionpack
- nokogiri
- rubyzip (>= 1.0)
- httparty (0.23.1)
- csv
- mini_mime (>= 1.0.0)
- multi_xml (>= 0.5.2)
- i18n (1.14.7)
- concurrent-ruby (~> 1.0)
- io-console (0.8.1)
- irb (1.15.2)
- pp (>= 0.6.0)
- rdoc (>= 4.0.0)
- reline (>= 0.4.2)
- jbuilder (2.14.1)
- actionview (>= 7.0.0)
- activesupport (>= 7.0.0)
- jsbundling-rails (1.3.1)
- railties (>= 6.0.0)
- json (2.15.1)
- json_schemer (2.4.0)
- bigdecimal
- hana (~> 1.3)
- regexp_parser (~> 2.0)
- simpleidn (~> 0.2)
- jwt (3.1.2)
- base64
- kaminari (1.2.2)
- activesupport (>= 4.1.0)
- kaminari-actionview (= 1.2.2)
- kaminari-activerecord (= 1.2.2)
- kaminari-core (= 1.2.2)
- kaminari-actionview (1.2.2)
- actionview
- kaminari-core (= 1.2.2)
- kaminari-activerecord (1.2.2)
- activerecord
- kaminari-core (= 1.2.2)
- kaminari-core (1.2.2)
- kramdown (2.5.1)
- rexml (>= 3.3.9)
- kramdown-parser-gfm (1.1.0)
- kramdown (~> 2.0)
- language_server-protocol (3.17.0.5)
- ledermann-rails-settings (2.6.2)
- activerecord (>= 6.1)
- lint_roller (1.1.0)
- listen (3.9.0)
- rb-fsevent (~> 0.10, >= 0.10.3)
- rb-inotify (~> 0.9, >= 0.9.10)
- locale (2.1.4)
- logger (1.7.0)
- loofah (2.24.1)
- crass (~> 1.0.2)
- nokogiri (>= 1.12.0)
- lumberjack (1.4.2)
- mail (2.7.1)
- mini_mime (>= 0.1.1)
- marcel (1.1.0)
- matrix (0.4.3)
- method_source (1.1.0)
- mime-types (3.7.0)
- logger
- mime-types-data (~> 3.2025, >= 3.2025.0507)
- mime-types-data (3.2025.0924)
- mimemagic (0.4.3)
- nokogiri (~> 1)
- rake
- mini_mime (1.1.5)
- minitest (5.25.5)
- mocha (2.7.1)
- ruby2_keywords (>= 0.0.5)
- msgpack (1.8.0)
- multi_json (1.17.0)
- multi_xml (0.7.1)
- bigdecimal (~> 3.1)
- mutex_m (0.3.0)
- mysql2 (0.5.7)
- bigdecimal
- nap (1.1.0)
- nenv (0.3.0)
- net-http (0.6.0)
- uri
- net-imap (0.5.12)
- date
- net-protocol
- net-pop (0.1.2)
- net-protocol
- net-protocol (0.2.2)
- timeout
- net-smtp (0.5.1)
- net-protocol
- nio4r (2.7.4)
- nokogiri (1.18.10-arm64-darwin)
- racc (~> 1.4)
- nokogiri (1.18.10-x86_64-linux-gnu)
- racc (~> 1.4)
- notiffany (0.1.3)
- nenv (~> 0.1)
- shellany (~> 0.0)
- oauth2 (2.0.17)
- faraday (>= 0.17.3, < 4.0)
- jwt (>= 1.0, < 4.0)
- logger (~> 1.2)
- multi_xml (~> 0.5)
- rack (>= 1.2, < 4)
- snaky_hash (~> 2.0, >= 2.0.3)
- version_gem (~> 1.1, >= 1.1.9)
- octokit (10.0.0)
- faraday (>= 1, < 3)
- sawyer (~> 0.9)
- omniauth (2.1.4)
- hashie (>= 3.4.6)
- logger
- rack (>= 2.2.3)
- rack-protection
- omniauth-oauth2 (1.8.0)
- oauth2 (>= 1.4, < 3)
- omniauth (~> 2.0)
- omniauth-orcid (2.1.1)
- omniauth-oauth2 (~> 1.3)
- ruby_dig (~> 0.0.2)
- omniauth-rails_csrf_protection (1.0.2)
- actionpack (>= 4.2)
- omniauth (~> 2.0)
- omniauth-shibboleth (1.3.0)
- omniauth (>= 1.0.0)
- open4 (1.3.4)
- options (2.3.2)
- orm_adapter (0.5.0)
- ostruct (0.6.3)
- parallel (1.27.0)
- parser (3.3.9.0)
- ast (~> 2.4.1)
- racc
- pg (1.6.2-arm64-darwin)
- pg (1.6.2-x86_64-linux)
- pp (0.6.3)
- prettyprint
- prettyprint (0.2.0)
- prime (0.1.4)
- forwardable
- singleton
- prism (1.5.1)
- process_executer (1.3.0)
- progress_bar (1.3.4)
- highline (>= 1.6)
- options (~> 2.3.0)
- pry (0.15.2)
- coderay (~> 1.1)
- method_source (~> 1.0)
- pstore (0.2.0)
- psych (5.2.6)
- date
- stringio
- public_suffix (6.0.2)
- puma (7.0.4)
- nio4r (~> 2.0)
- pundit (2.5.2)
- activesupport (>= 3.0.0)
- pundit-matchers (4.0.0)
- rspec-core (~> 3.12)
- rspec-expectations (~> 3.12)
- rspec-mocks (~> 3.12)
- rspec-support (~> 3.12)
- racc (1.8.1)
- rack (3.2.2)
- rack-attack (6.7.0)
- rack (>= 1.0, < 4)
- rack-mini-profiler (4.0.1)
- rack (>= 1.2.0)
- rack-protection (4.1.1)
- base64 (>= 0.1.0)
- logger (>= 1.6.0)
- rack (>= 3.0.0, < 4)
- rack-session (2.1.1)
- base64 (>= 0.1.0)
- rack (>= 3.0.0)
- rack-test (2.2.0)
- rack (>= 1.3)
- rackup (2.2.1)
- rack (>= 3)
- rails (7.1.5.2)
- actioncable (= 7.1.5.2)
- actionmailbox (= 7.1.5.2)
- actionmailer (= 7.1.5.2)
- actionpack (= 7.1.5.2)
- actiontext (= 7.1.5.2)
- actionview (= 7.1.5.2)
- activejob (= 7.1.5.2)
- activemodel (= 7.1.5.2)
- activerecord (= 7.1.5.2)
- activestorage (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- bundler (>= 1.15.0)
- railties (= 7.1.5.2)
- rails-controller-testing (1.0.5)
- actionpack (>= 5.0.1.rc1)
- actionview (>= 5.0.1.rc1)
- activesupport (>= 5.0.1.rc1)
- rails-dom-testing (2.3.0)
- activesupport (>= 5.0.0)
- minitest
- nokogiri (>= 1.6)
- rails-html-sanitizer (1.6.2)
- loofah (~> 2.21)
- nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
- railties (7.1.5.2)
- actionpack (= 7.1.5.2)
- activesupport (= 7.1.5.2)
- irb
- rackup (>= 1.0.0)
- rake (>= 12.2)
- thor (~> 1.0, >= 1.2.2)
- zeitwerk (~> 2.6)
- rainbow (3.1.1)
- rake (13.3.0)
- rb-fsevent (0.11.2)
- rb-inotify (0.11.1)
- ffi (~> 1.0)
- rchardet (1.10.0)
- rdoc (6.15.0)
- erb
- psych (>= 4.0.0)
- tsort
- recaptcha (5.21.1)
- regexp_parser (2.11.3)
- reline (0.6.2)
- io-console (~> 0.5)
- responders (3.1.1)
- actionpack (>= 5.2)
- railties (>= 5.2)
- rexml (3.4.4)
- rollbar (3.6.2)
- rouge (4.6.1)
- rspec-collection_matchers (1.2.1)
- rspec-expectations (>= 2.99.0.beta1)
- rspec-core (3.13.5)
- 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.5)
- diff-lcs (>= 1.2.0, < 2.0)
- rspec-support (~> 3.13.0)
- rspec-rails (7.1.1)
- actionpack (>= 7.0)
- activesupport (>= 7.0)
- railties (>= 7.0)
- rspec-core (~> 3.13)
- rspec-expectations (~> 3.13)
- rspec-mocks (~> 3.13)
- rspec-support (~> 3.13)
- rspec-support (3.13.6)
- rubocop (1.81.1)
- json (~> 2.3)
- language_server-protocol (~> 3.17.0.2)
- lint_roller (~> 1.1.0)
- parallel (~> 1.10)
- parser (>= 3.3.0.2)
- rainbow (>= 2.2.2, < 4.0)
- regexp_parser (>= 2.9.3, < 3.0)
- rubocop-ast (>= 1.47.1, < 2.0)
- ruby-progressbar (~> 1.7)
- unicode-display_width (>= 2.4.0, < 4.0)
- rubocop-ast (1.47.1)
- parser (>= 3.3.7.2)
- prism (~> 1.4)
- rubocop-i18n (3.2.3)
- lint_roller (~> 1.1)
- rubocop (>= 1.72.1)
- rubocop-performance (1.26.0)
- lint_roller (~> 1.1)
- rubocop (>= 1.75.0, < 2.0)
- rubocop-ast (>= 1.44.0, < 2.0)
- ruby-progressbar (1.13.0)
- ruby2_keywords (0.0.5)
- ruby_dig (0.0.2)
- rubyzip (2.4.1)
- sawyer (0.9.2)
- addressable (>= 2.3.5)
- faraday (>= 0.17.3, < 3)
- securerandom (0.4.1)
- selenium-webdriver (4.32.0)
- base64 (~> 0.2)
- logger (~> 1.4)
- rexml (~> 3.2, >= 3.2.5)
- rubyzip (>= 1.2.2, < 3.0)
- websocket (~> 1.0)
- shellany (0.0.1)
- shoulda (4.0.0)
- shoulda-context (~> 2.0)
- shoulda-matchers (~> 4.0)
- shoulda-context (2.0.0)
- shoulda-matchers (4.5.1)
- activesupport (>= 4.2.0)
- simpleidn (0.2.3)
- singleton (0.3.0)
- snaky_hash (2.0.3)
- hashie (>= 0.1.0, < 6)
- version_gem (>= 1.1.8, < 3)
- spring (4.4.0)
- spring-commands-rspec (1.0.4)
- spring (>= 0.9.1)
- spring-watcher-listen (2.1.0)
- listen (>= 2.7, < 4.0)
- spring (>= 4)
- sprockets (4.2.2)
- concurrent-ruby (~> 1.0)
- logger
- rack (>= 2.2.4, < 4)
- sprockets-rails (3.5.2)
- actionpack (>= 6.1)
- activesupport (>= 6.1)
- sprockets (>= 3.0.0)
- stringio (3.1.7)
- terminal-table (4.0.0)
- unicode-display_width (>= 1.1.1, < 4)
- text (1.3.1)
- thor (1.4.0)
- timeout (0.4.3)
- tomparse (0.4.2)
- translation (1.41)
- gettext (~> 3.2, >= 3.2.5, <= 3.4.9)
- tsort (0.2.0)
- turbo-rails (2.0.17)
- actionpack (>= 7.1.0)
- railties (>= 7.1.0)
- tzinfo (2.0.6)
- concurrent-ruby (~> 1.0)
- unicode-display_width (3.2.0)
- unicode-emoji (~> 4.1)
- unicode-emoji (4.1.0)
- uniform_notifier (1.18.0)
- uri (1.0.4)
- version_gem (1.1.9)
- warden (1.2.9)
- rack (>= 2.0.9)
- web-console (4.2.1)
- actionview (>= 6.0.0)
- activemodel (>= 6.0.0)
- bindex (>= 0.4.0)
- railties (>= 6.0.0)
- webmock (3.25.1)
- addressable (>= 2.8.0)
- crack (>= 0.3.2)
- hashdiff (>= 0.4.0, < 2.0.0)
- websocket (1.2.11)
- websocket-driver (0.8.0)
- base64
- websocket-extensions (>= 0.1.0)
- websocket-extensions (0.1.5)
- wicked_pdf (2.8.2)
- activesupport
- ostruct
- wkhtmltopdf-binary (0.12.6.10)
- xpath (3.2.0)
- nokogiri (~> 1.8)
- yard (0.9.37)
- yard-tomdoc (0.7.1)
- tomparse (>= 0.4.0)
- yard
- zeitwerk (2.6.18)
-
-PLATFORMS
- arm64-darwin-21
- arm64-darwin-22
- x86_64-linux
-
-DEPENDENCIES
- activerecord_json_validator
- annotate
- annotate_gem
- api-pagination
- autoprefixer-rails
- better_errors
- binding_of_caller
- bootsnap
- brakeman
- bullet
- bundle-audit
- byebug
- capybara
- contact_us
- cssbundling-rails
- danger
- database_cleaner
- devise
- devise_invitable
- dotenv-rails
- dragonfly
- dragonfly-s3_data_store
- factory_bot_rails
- faker
- flag_shih_tzu
- fuubar
- guard
- htmltoword
- httparty
- jbuilder
- jsbundling-rails
- jwt
- kaminari
- ledermann-rails-settings
- listen
- mail (= 2.7.1)
- mimemagic
- mocha
- mysql2
- net-smtp
- omniauth
- omniauth-orcid
- omniauth-rails_csrf_protection
- omniauth-shibboleth
- parallel
- pg
- progress_bar
- puma
- pundit
- pundit-matchers
- rack-attack (~> 6.6, >= 6.6.1)
- rack-mini-profiler
- rails (~> 7.1)
- rails-controller-testing
- recaptcha
- rollbar
- rspec-collection_matchers
- rspec-rails
- rubocop
- rubocop-i18n
- rubocop-performance
- ruby-progressbar
- selenium-webdriver
- shoulda
- spring
- spring-commands-rspec
- spring-watcher-listen
- sprockets-rails
- text
- translation
- turbo-rails
- web-console
- webmock
- wicked_pdf
- wkhtmltopdf-binary
- yard
- yard-tomdoc
-
-RUBY VERSION
- ruby 3.1.4p223
-
-BUNDLED WITH
- 2.4.17
+GEM
+ remote: https://rubygems.org/
+ specs:
+ actioncable (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ nio4r (~> 2.0)
+ websocket-driver (>= 0.6.1)
+ zeitwerk (~> 2.6)
+ actionmailbox (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ activejob (= 7.1.5.2)
+ activerecord (= 7.1.5.2)
+ activestorage (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ mail (>= 2.7.1)
+ net-imap
+ net-pop
+ net-smtp
+ actionmailer (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ actionview (= 7.1.5.2)
+ activejob (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ mail (~> 2.5, >= 2.5.4)
+ net-imap
+ net-pop
+ net-smtp
+ rails-dom-testing (~> 2.2)
+ actionpack (7.1.5.2)
+ actionview (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ nokogiri (>= 1.8.5)
+ racc
+ rack (>= 2.2.4)
+ rack-session (>= 1.0.1)
+ rack-test (>= 0.6.3)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ actiontext (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ activerecord (= 7.1.5.2)
+ activestorage (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ globalid (>= 0.6.0)
+ nokogiri (>= 1.8.5)
+ actionview (7.1.5.2)
+ activesupport (= 7.1.5.2)
+ builder (~> 3.1)
+ erubi (~> 1.11)
+ rails-dom-testing (~> 2.2)
+ rails-html-sanitizer (~> 1.6)
+ activejob (7.1.5.2)
+ activesupport (= 7.1.5.2)
+ globalid (>= 0.3.6)
+ activemodel (7.1.5.2)
+ activesupport (= 7.1.5.2)
+ activerecord (7.1.5.2)
+ activemodel (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ timeout (>= 0.4.0)
+ activerecord_json_validator (3.1.0)
+ activerecord (>= 4.2.0, < 9)
+ json_schemer (~> 2.2)
+ activestorage (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ activejob (= 7.1.5.2)
+ activerecord (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ marcel (~> 1.0)
+ activesupport (7.1.5.2)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.0.2)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ mutex_m
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0)
+ addressable (2.8.7)
+ public_suffix (>= 2.0.2, < 7.0)
+ annotate (3.2.0)
+ activerecord (>= 3.2, < 8.0)
+ rake (>= 10.4, < 14.0)
+ annotate_gem (0.0.14)
+ bundler (>= 1.1)
+ api-pagination (6.0.0)
+ ast (2.4.3)
+ autoprefixer-rails (10.4.21.0)
+ execjs (~> 2)
+ base64 (0.3.0)
+ bcrypt (3.1.20)
+ benchmark (0.4.1)
+ better_errors (2.10.1)
+ erubi (>= 1.0.0)
+ rack (>= 0.9.0)
+ rouge (>= 1.0.0)
+ bigdecimal (3.3.0)
+ bindex (0.8.1)
+ binding_of_caller (1.0.1)
+ debug_inspector (>= 1.2.0)
+ bootsnap (1.18.6)
+ msgpack (~> 1.2)
+ brakeman (7.1.0)
+ racc
+ builder (3.3.0)
+ bullet (8.0.8)
+ activesupport (>= 3.0.0)
+ uniform_notifier (~> 1.11)
+ bundle-audit (0.1.0)
+ bundler-audit
+ bundler-audit (0.9.2)
+ bundler (>= 1.2.0, < 3)
+ thor (~> 1.0)
+ byebug (12.0.0)
+ capybara (3.40.0)
+ addressable
+ matrix
+ mini_mime (>= 0.1.3)
+ nokogiri (~> 1.11)
+ rack (>= 1.6.0)
+ rack-test (>= 0.6.3)
+ regexp_parser (>= 1.5, < 3.0)
+ xpath (~> 3.2)
+ cgi (0.5.0)
+ claide (1.1.0)
+ claide-plugins (0.9.2)
+ cork
+ nap
+ open4 (~> 1.3)
+ coderay (1.1.3)
+ colored2 (3.1.2)
+ concurrent-ruby (1.3.5)
+ connection_pool (2.5.4)
+ contact_us (1.2.0)
+ rails (>= 4.2.0)
+ cork (0.3.0)
+ colored2 (~> 3.1)
+ crack (1.0.0)
+ bigdecimal
+ rexml
+ crass (1.0.6)
+ cssbundling-rails (1.4.3)
+ railties (>= 6.0.0)
+ csv (3.3.5)
+ danger (9.5.3)
+ base64 (~> 0.2)
+ claide (~> 1.0)
+ claide-plugins (>= 0.9.2)
+ colored2 (>= 3.1, < 5)
+ cork (~> 0.1)
+ faraday (>= 0.9.0, < 3.0)
+ faraday-http-cache (~> 2.0)
+ git (>= 1.13, < 3.0)
+ kramdown (>= 2.5.1, < 3.0)
+ kramdown-parser-gfm (~> 1.0)
+ octokit (>= 4.0)
+ pstore (~> 0.1)
+ terminal-table (>= 1, < 5)
+ database_cleaner (2.1.0)
+ database_cleaner-active_record (>= 2, < 3)
+ database_cleaner-active_record (2.2.2)
+ activerecord (>= 5.a)
+ database_cleaner-core (~> 2.0)
+ database_cleaner-core (2.0.1)
+ date (3.4.1)
+ debug_inspector (1.2.0)
+ devise (4.9.4)
+ bcrypt (~> 3.0)
+ orm_adapter (~> 0.1)
+ railties (>= 4.1.0)
+ responders
+ warden (~> 1.2.3)
+ devise_invitable (2.0.11)
+ actionmailer (>= 5.0)
+ devise (>= 4.6)
+ diff-lcs (1.6.2)
+ doorkeeper (5.8.2)
+ railties (>= 5)
+ dotenv (3.1.8)
+ dotenv-rails (3.1.8)
+ dotenv (= 3.1.8)
+ railties (>= 6.1)
+ dragonfly (1.4.1)
+ addressable (~> 2.3)
+ multi_json (~> 1.0)
+ ostruct (~> 0.6.1)
+ rack (>= 1.3)
+ dragonfly-s3_data_store (1.3.0)
+ dragonfly (~> 1.0)
+ fog-aws
+ drb (2.2.3)
+ erb (4.0.4)
+ cgi (>= 0.3.3)
+ erubi (1.13.1)
+ excon (1.3.0)
+ logger
+ execjs (2.10.0)
+ factory_bot (6.5.5)
+ activesupport (>= 6.1.0)
+ factory_bot_rails (6.5.1)
+ factory_bot (~> 6.5)
+ railties (>= 6.1.0)
+ faker (3.5.2)
+ i18n (>= 1.8.11, < 2)
+ faraday (2.14.0)
+ faraday-net_http (>= 2.0, < 3.5)
+ json
+ logger
+ faraday-http-cache (2.5.1)
+ faraday (>= 0.8)
+ faraday-net_http (3.4.1)
+ net-http (>= 0.5.0)
+ ffi (1.17.2-aarch64-linux-gnu)
+ ffi (1.17.2-arm64-darwin)
+ ffi (1.17.2-x86_64-linux-gnu)
+ flag_shih_tzu (0.3.23)
+ fog-aws (3.33.0)
+ base64 (>= 0.2, < 0.4)
+ fog-core (~> 2.6)
+ fog-json (~> 1.1)
+ fog-xml (~> 0.1)
+ fog-core (2.6.0)
+ builder
+ excon (~> 1.0)
+ formatador (>= 0.2, < 2.0)
+ mime-types
+ fog-json (1.2.0)
+ fog-core
+ multi_json (~> 1.10)
+ fog-xml (0.1.5)
+ fog-core
+ nokogiri (>= 1.5.11, < 2.0.0)
+ formatador (1.2.1)
+ reline
+ forwardable (1.3.3)
+ fuubar (2.5.1)
+ rspec-core (~> 3.0)
+ ruby-progressbar (~> 1.4)
+ gettext (3.4.9)
+ erubi
+ locale (>= 2.0.5)
+ prime
+ racc
+ text (>= 1.3.0)
+ git (2.3.3)
+ activesupport (>= 5.0)
+ addressable (~> 2.8)
+ process_executer (~> 1.1)
+ rchardet (~> 1.8)
+ globalid (1.3.0)
+ activesupport (>= 6.1)
+ guard (2.19.1)
+ formatador (>= 0.2.4)
+ listen (>= 2.7, < 4.0)
+ logger (~> 1.6)
+ lumberjack (>= 1.0.12, < 2.0)
+ nenv (~> 0.1)
+ notiffany (~> 0.0)
+ ostruct (~> 0.6)
+ pry (>= 0.13.0)
+ shellany (~> 0.0)
+ thor (>= 0.18.1)
+ hana (1.3.7)
+ hashdiff (1.2.1)
+ hashie (5.0.0)
+ highline (3.1.2)
+ reline
+ htmltoword (1.1.1)
+ actionpack
+ nokogiri
+ rubyzip (>= 1.0)
+ httparty (0.23.1)
+ csv
+ mini_mime (>= 1.0.0)
+ multi_xml (>= 0.5.2)
+ i18n (1.14.7)
+ concurrent-ruby (~> 1.0)
+ io-console (0.8.1)
+ irb (1.15.2)
+ pp (>= 0.6.0)
+ rdoc (>= 4.0.0)
+ reline (>= 0.4.2)
+ jbuilder (2.14.1)
+ actionview (>= 7.0.0)
+ activesupport (>= 7.0.0)
+ jsbundling-rails (1.3.1)
+ railties (>= 6.0.0)
+ json (2.15.1)
+ json_schemer (2.4.0)
+ bigdecimal
+ hana (~> 1.3)
+ regexp_parser (~> 2.0)
+ simpleidn (~> 0.2)
+ jwt (3.1.2)
+ base64
+ kaminari (1.2.2)
+ activesupport (>= 4.1.0)
+ kaminari-actionview (= 1.2.2)
+ kaminari-activerecord (= 1.2.2)
+ kaminari-core (= 1.2.2)
+ kaminari-actionview (1.2.2)
+ actionview
+ kaminari-core (= 1.2.2)
+ kaminari-activerecord (1.2.2)
+ activerecord
+ kaminari-core (= 1.2.2)
+ kaminari-core (1.2.2)
+ kramdown (2.5.1)
+ rexml (>= 3.3.9)
+ kramdown-parser-gfm (1.1.0)
+ kramdown (~> 2.0)
+ language_server-protocol (3.17.0.5)
+ ledermann-rails-settings (2.6.2)
+ activerecord (>= 6.1)
+ lint_roller (1.1.0)
+ listen (3.9.0)
+ rb-fsevent (~> 0.10, >= 0.10.3)
+ rb-inotify (~> 0.9, >= 0.9.10)
+ locale (2.1.4)
+ logger (1.7.0)
+ loofah (2.24.1)
+ crass (~> 1.0.2)
+ nokogiri (>= 1.12.0)
+ lumberjack (1.4.2)
+ mail (2.7.1)
+ mini_mime (>= 0.1.1)
+ marcel (1.1.0)
+ matrix (0.4.3)
+ method_source (1.1.0)
+ mime-types (3.7.0)
+ logger
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
+ mime-types-data (3.2025.0924)
+ mimemagic (0.4.3)
+ nokogiri (~> 1)
+ rake
+ mini_mime (1.1.5)
+ minitest (5.25.5)
+ mocha (2.7.1)
+ ruby2_keywords (>= 0.0.5)
+ msgpack (1.8.0)
+ multi_json (1.17.0)
+ multi_xml (0.7.1)
+ bigdecimal (~> 3.1)
+ mutex_m (0.3.0)
+ mysql2 (0.5.7)
+ bigdecimal
+ nap (1.1.0)
+ nenv (0.3.0)
+ net-http (0.6.0)
+ uri
+ net-imap (0.5.12)
+ date
+ net-protocol
+ net-pop (0.1.2)
+ net-protocol
+ net-protocol (0.2.2)
+ timeout
+ net-smtp (0.5.1)
+ net-protocol
+ nio4r (2.7.4)
+ nokogiri (1.18.10-aarch64-linux-gnu)
+ racc (~> 1.4)
+ nokogiri (1.18.10-arm64-darwin)
+ racc (~> 1.4)
+ nokogiri (1.18.10-x86_64-linux-gnu)
+ racc (~> 1.4)
+ notiffany (0.1.3)
+ nenv (~> 0.1)
+ shellany (~> 0.0)
+ oauth2 (2.0.17)
+ faraday (>= 0.17.3, < 4.0)
+ jwt (>= 1.0, < 4.0)
+ logger (~> 1.2)
+ multi_xml (~> 0.5)
+ rack (>= 1.2, < 4)
+ snaky_hash (~> 2.0, >= 2.0.3)
+ version_gem (~> 1.1, >= 1.1.9)
+ octokit (10.0.0)
+ faraday (>= 1, < 3)
+ sawyer (~> 0.9)
+ omniauth (2.1.4)
+ hashie (>= 3.4.6)
+ logger
+ rack (>= 2.2.3)
+ rack-protection
+ omniauth-oauth2 (1.8.0)
+ oauth2 (>= 1.4, < 3)
+ omniauth (~> 2.0)
+ omniauth-orcid (2.1.1)
+ omniauth-oauth2 (~> 1.3)
+ ruby_dig (~> 0.0.2)
+ omniauth-rails_csrf_protection (1.0.2)
+ actionpack (>= 4.2)
+ omniauth (~> 2.0)
+ omniauth-shibboleth (1.3.0)
+ omniauth (>= 1.0.0)
+ open4 (1.3.4)
+ options (2.3.2)
+ orm_adapter (0.5.0)
+ ostruct (0.6.3)
+ parallel (1.27.0)
+ parser (3.3.9.0)
+ ast (~> 2.4.1)
+ racc
+ pg (1.6.2-aarch64-linux)
+ pg (1.6.2-arm64-darwin)
+ pg (1.6.2-x86_64-linux)
+ pp (0.6.3)
+ prettyprint
+ prettyprint (0.2.0)
+ prime (0.1.4)
+ forwardable
+ singleton
+ prism (1.5.1)
+ process_executer (1.3.0)
+ progress_bar (1.3.4)
+ highline (>= 1.6)
+ options (~> 2.3.0)
+ pry (0.15.2)
+ coderay (~> 1.1)
+ method_source (~> 1.0)
+ pstore (0.2.0)
+ psych (5.2.6)
+ date
+ stringio
+ public_suffix (6.0.2)
+ puma (7.0.4)
+ nio4r (~> 2.0)
+ pundit (2.5.2)
+ activesupport (>= 3.0.0)
+ pundit-matchers (4.0.0)
+ rspec-core (~> 3.12)
+ rspec-expectations (~> 3.12)
+ rspec-mocks (~> 3.12)
+ rspec-support (~> 3.12)
+ racc (1.8.1)
+ rack (3.2.2)
+ rack-attack (6.7.0)
+ rack (>= 1.0, < 4)
+ rack-mini-profiler (4.0.1)
+ rack (>= 1.2.0)
+ rack-protection (4.1.1)
+ base64 (>= 0.1.0)
+ logger (>= 1.6.0)
+ rack (>= 3.0.0, < 4)
+ rack-session (2.1.1)
+ base64 (>= 0.1.0)
+ rack (>= 3.0.0)
+ rack-test (2.2.0)
+ rack (>= 1.3)
+ rackup (2.2.1)
+ rack (>= 3)
+ rails (7.1.5.2)
+ actioncable (= 7.1.5.2)
+ actionmailbox (= 7.1.5.2)
+ actionmailer (= 7.1.5.2)
+ actionpack (= 7.1.5.2)
+ actiontext (= 7.1.5.2)
+ actionview (= 7.1.5.2)
+ activejob (= 7.1.5.2)
+ activemodel (= 7.1.5.2)
+ activerecord (= 7.1.5.2)
+ activestorage (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ bundler (>= 1.15.0)
+ railties (= 7.1.5.2)
+ rails-controller-testing (1.0.5)
+ actionpack (>= 5.0.1.rc1)
+ actionview (>= 5.0.1.rc1)
+ activesupport (>= 5.0.1.rc1)
+ rails-dom-testing (2.3.0)
+ activesupport (>= 5.0.0)
+ minitest
+ nokogiri (>= 1.6)
+ rails-html-sanitizer (1.6.2)
+ loofah (~> 2.21)
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
+ railties (7.1.5.2)
+ actionpack (= 7.1.5.2)
+ activesupport (= 7.1.5.2)
+ irb
+ rackup (>= 1.0.0)
+ rake (>= 12.2)
+ thor (~> 1.0, >= 1.2.2)
+ zeitwerk (~> 2.6)
+ rainbow (3.1.1)
+ rake (13.3.0)
+ rb-fsevent (0.11.2)
+ rb-inotify (0.11.1)
+ ffi (~> 1.0)
+ rchardet (1.10.0)
+ rdoc (6.15.0)
+ erb
+ psych (>= 4.0.0)
+ tsort
+ recaptcha (5.21.1)
+ regexp_parser (2.11.3)
+ reline (0.6.2)
+ io-console (~> 0.5)
+ responders (3.1.1)
+ actionpack (>= 5.2)
+ railties (>= 5.2)
+ rexml (3.4.4)
+ rollbar (3.6.2)
+ rouge (4.6.1)
+ rspec-collection_matchers (1.2.1)
+ rspec-expectations (>= 2.99.0.beta1)
+ rspec-core (3.13.5)
+ 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.5)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.13.0)
+ rspec-rails (7.1.1)
+ actionpack (>= 7.0)
+ activesupport (>= 7.0)
+ railties (>= 7.0)
+ rspec-core (~> 3.13)
+ rspec-expectations (~> 3.13)
+ rspec-mocks (~> 3.13)
+ rspec-support (~> 3.13)
+ rspec-support (3.13.6)
+ rubocop (1.81.1)
+ json (~> 2.3)
+ language_server-protocol (~> 3.17.0.2)
+ lint_roller (~> 1.1.0)
+ parallel (~> 1.10)
+ parser (>= 3.3.0.2)
+ rainbow (>= 2.2.2, < 4.0)
+ regexp_parser (>= 2.9.3, < 3.0)
+ rubocop-ast (>= 1.47.1, < 2.0)
+ ruby-progressbar (~> 1.7)
+ unicode-display_width (>= 2.4.0, < 4.0)
+ rubocop-ast (1.47.1)
+ parser (>= 3.3.7.2)
+ prism (~> 1.4)
+ rubocop-i18n (3.2.3)
+ lint_roller (~> 1.1)
+ rubocop (>= 1.72.1)
+ rubocop-performance (1.26.0)
+ lint_roller (~> 1.1)
+ rubocop (>= 1.75.0, < 2.0)
+ rubocop-ast (>= 1.44.0, < 2.0)
+ ruby-progressbar (1.13.0)
+ ruby2_keywords (0.0.5)
+ ruby_dig (0.0.2)
+ rubyzip (2.4.1)
+ sawyer (0.9.2)
+ addressable (>= 2.3.5)
+ faraday (>= 0.17.3, < 3)
+ securerandom (0.4.1)
+ selenium-webdriver (4.32.0)
+ base64 (~> 0.2)
+ logger (~> 1.4)
+ rexml (~> 3.2, >= 3.2.5)
+ rubyzip (>= 1.2.2, < 3.0)
+ websocket (~> 1.0)
+ shellany (0.0.1)
+ shoulda (4.0.0)
+ shoulda-context (~> 2.0)
+ shoulda-matchers (~> 4.0)
+ shoulda-context (2.0.0)
+ shoulda-matchers (4.5.1)
+ activesupport (>= 4.2.0)
+ simpleidn (0.2.3)
+ singleton (0.3.0)
+ snaky_hash (2.0.3)
+ hashie (>= 0.1.0, < 6)
+ version_gem (>= 1.1.8, < 3)
+ spring (4.4.0)
+ spring-commands-rspec (1.0.4)
+ spring (>= 0.9.1)
+ spring-watcher-listen (2.1.0)
+ listen (>= 2.7, < 4.0)
+ spring (>= 4)
+ sprockets (4.2.2)
+ concurrent-ruby (~> 1.0)
+ logger
+ rack (>= 2.2.4, < 4)
+ sprockets-rails (3.5.2)
+ actionpack (>= 6.1)
+ activesupport (>= 6.1)
+ sprockets (>= 3.0.0)
+ stringio (3.1.7)
+ terminal-table (4.0.0)
+ unicode-display_width (>= 1.1.1, < 4)
+ text (1.3.1)
+ thor (1.4.0)
+ timeout (0.4.3)
+ tomparse (0.4.2)
+ translation (1.41)
+ gettext (~> 3.2, >= 3.2.5, <= 3.4.9)
+ tsort (0.2.0)
+ turbo-rails (2.0.17)
+ actionpack (>= 7.1.0)
+ railties (>= 7.1.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ unicode-display_width (3.2.0)
+ unicode-emoji (~> 4.1)
+ unicode-emoji (4.1.0)
+ uniform_notifier (1.18.0)
+ uri (1.0.4)
+ version_gem (1.1.9)
+ warden (1.2.9)
+ rack (>= 2.0.9)
+ web-console (4.2.1)
+ actionview (>= 6.0.0)
+ activemodel (>= 6.0.0)
+ bindex (>= 0.4.0)
+ railties (>= 6.0.0)
+ webmock (3.25.1)
+ addressable (>= 2.8.0)
+ crack (>= 0.3.2)
+ hashdiff (>= 0.4.0, < 2.0.0)
+ websocket (1.2.11)
+ websocket-driver (0.8.0)
+ base64
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.5)
+ wicked_pdf (2.8.2)
+ activesupport
+ ostruct
+ wkhtmltopdf-binary (0.12.6.10)
+ xpath (3.2.0)
+ nokogiri (~> 1.8)
+ yard (0.9.37)
+ yard-tomdoc (0.7.1)
+ tomparse (>= 0.4.0)
+ yard
+ zeitwerk (2.6.18)
+
+PLATFORMS
+ aarch64-linux
+ arm64-darwin-21
+ arm64-darwin-22
+ x86_64-linux
+
+DEPENDENCIES
+ activerecord_json_validator
+ annotate
+ annotate_gem
+ api-pagination
+ autoprefixer-rails
+ better_errors
+ binding_of_caller
+ bootsnap
+ brakeman
+ bullet
+ bundle-audit
+ byebug
+ capybara
+ contact_us
+ cssbundling-rails
+ danger
+ database_cleaner
+ devise
+ devise_invitable
+ doorkeeper
+ dotenv-rails
+ dragonfly
+ dragonfly-s3_data_store
+ factory_bot_rails
+ faker
+ flag_shih_tzu
+ fuubar
+ guard
+ htmltoword
+ httparty
+ jbuilder
+ jsbundling-rails
+ jwt
+ kaminari
+ ledermann-rails-settings
+ listen
+ mail (= 2.7.1)
+ mimemagic
+ mocha
+ mysql2
+ net-smtp
+ omniauth
+ omniauth-orcid
+ omniauth-rails_csrf_protection
+ omniauth-shibboleth
+ parallel
+ pg
+ progress_bar
+ puma
+ pundit
+ pundit-matchers
+ rack-attack (~> 6.6, >= 6.6.1)
+ rack-mini-profiler
+ rails (~> 7.1)
+ rails-controller-testing
+ recaptcha
+ rollbar
+ rspec-collection_matchers
+ rspec-rails
+ rubocop
+ rubocop-i18n
+ rubocop-performance
+ ruby-progressbar
+ selenium-webdriver
+ shoulda
+ spring
+ spring-commands-rspec
+ spring-watcher-listen
+ sprockets-rails
+ text
+ translation
+ turbo-rails
+ web-console
+ webmock
+ wicked_pdf
+ wkhtmltopdf-binary
+ yard
+ yard-tomdoc
+
+RUBY VERSION
+ ruby 3.1.4p223
+
+BUNDLED WITH
+ 2.4.17
diff --git a/app/controllers/api/v2/base_api_controller.rb b/app/controllers/api/v2/base_api_controller.rb
new file mode 100644
index 0000000000..f0f4f15bd0
--- /dev/null
+++ b/app/controllers/api/v2/base_api_controller.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ class BaseApiController < ApplicationController # rubocop:todo Style/Documentation
+ # skipping the standard rails authenticity tokens passed in the UI
+ skip_before_action :verify_authenticity_token
+
+ # call doorkeeper to authorize the request
+ before_action :doorkeeper_authorize!, except: %i[heartbeat]
+ # get details of server (e.g. DMPonline) and client app
+ before_action :base_response_content
+
+ before_action :log_access
+
+ # controller can respond to json format requests
+ respond_to :json
+
+ # set up pages in response
+ before_action :pagination_params, except: %i[heartbeat]
+
+ rescue_from Exception, with: :handle_exception
+
+ # GET /api/v2/heartbeat
+ def heartbeat
+ render '/api/v2/heartbeat'
+ end
+
+ # GET /me.json - recommended for doorkeeper gem
+ def me
+ respond_with @resource_owner
+ end
+
+ private
+
+ # define instance variable json and associated getter and setter methods
+ attr_accessor :json
+
+ # rubocop:disable Metrics/AbcSize
+ def base_response_content
+ @application = ApplicationService.application_name
+ @caller = request.remote_ip if @client.blank?
+ @caller = @client.is_a?(User) ? @client.name(false) : @client.name if @client.present?
+ @scopes = doorkeeper_token.scopes.to_a if doorkeeper_token
+ return unless doorkeeper_token&.resource_owner_id
+
+ @resource_owner = User.find(doorkeeper_token.resource_owner_id)
+ end
+ # rubocop:enable Metrics/AbcSize
+
+ def log_access
+ if @client.present?
+ Rails.logger.info "Client (OAuth) application name: #{@client.name}"
+ Rails.logger.info "Client (OAuth) application uid: #{@client.uid}"
+ end
+ Rails.logger.info "Resource owner id: #{@resource_owner.id}" if @resource_owner
+ end
+
+ def handle_exception(exception)
+ if exception.is_a?(Pundit::NotAuthorizedError)
+ handle_client_not_authorized
+ else
+ handle_internal_server_error(exception)
+ end
+ end
+
+ def handle_internal_server_error(exception)
+ # log server errors
+ Rails.logger.error "Exception message: #{exception.message}"
+
+ # inform client of server error
+ message = _('There was a problem in the server.')
+ @payload = { message: [message] }
+ render '/api/v2/error', status: :internal_server_error
+ end
+
+ def handle_client_not_authorized
+ message = _('The client is not authorized to perform this action.')
+ @payload = { message: [message] }
+ render '/api/v2/error', status: :forbidden
+ end
+
+ # retrieve the requested pagination params or use defaults
+ # only allow 100 per page as the max
+ def pagination_params
+ max_per_page = Rails.configuration.x.application.api_max_page_size
+ @page = params.fetch('page', 1).to_i
+ @per_page = params.fetch('per_page', max_per_page).to_i
+ @per_page = max_per_page if @per_page > max_per_page
+ end
+
+ def paginate_response(results:)
+ results = results.page(@page).per(@per_page)
+ @total_items = results.total_count
+ results
+ end
+ end
+ end
+end
diff --git a/app/controllers/api/v2/plans_controller.rb b/app/controllers/api/v2/plans_controller.rb
new file mode 100644
index 0000000000..7714e9c892
--- /dev/null
+++ b/app/controllers/api/v2/plans_controller.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ class PlansController < BaseApiController # rubocop:todo Style/Documentation
+ respond_to :json
+
+ # GET /api/v2/plans/:id
+ def show
+ raise Pundit::NotAuthorizedError unless @scopes.include?('read')
+
+ @plan = Plan.find_by(id: params[:id])
+
+ raise Pundit::NotAuthorizedError unless @plan.present?
+
+ plans_policy = PlansPolicy.new(@resource_owner, @plan)
+ raise Pundit::NotAuthorizedError unless plans_policy.show?
+
+ @items = [@plan]
+ render '/api/v2/plans/index', status: :ok
+ end
+
+ # GET /api/v2/plans
+ def index
+ raise Pundit::NotAuthorizedError unless @scopes.include?('read')
+
+ @plans = PlansPolicy::Scope.new(@resource_owner).resolve
+ @items = paginate_response(results: @plans)
+ render '/api/v2/plans/index', status: :ok
+ end
+ end
+ end
+end
diff --git a/app/controllers/api/v2/templates_controller.rb b/app/controllers/api/v2/templates_controller.rb
new file mode 100644
index 0000000000..5e732506dc
--- /dev/null
+++ b/app/controllers/api/v2/templates_controller.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # provides a list of templates for API V2
+ class TemplatesController < BaseApiController
+ respond_to :json
+
+ # GET /api/v2/templates
+ def index
+ raise Pundit::NotAuthorizedError unless @scopes.include?('read')
+
+ templates = Api::V2::TemplatesPolicy::Scope.new(@resource_owner).resolve
+ @items = paginate_response(results: templates)
+ render '/api/v2/templates/index', status: :ok
+ end
+ end
+ end
+end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1e2c29fcce..e5d2421e76 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -66,6 +66,9 @@ def store_location
# rubocop:disable Metrics/AbcSize
def after_sign_in_path_for(_resource)
+ # ensure oauth2 authorization flow is not interrupted
+ return session[:user_return_to] if user_is_in_oauth_flow
+
referer_path = URI(request.referer).path unless request.referer.nil?
if from_external_domain? || referer_path.eql?(new_user_session_path) ||
referer_path.eql?(new_user_registration_path) ||
@@ -77,7 +80,10 @@ def after_sign_in_path_for(_resource)
end
# rubocop:enable Metrics/AbcSize
- def after_sign_up_path_for(_resource)
+ def after_sign_up_path_for(_resource) # rubocop:todo Metrics/AbcSize
+ # ensure oauth2 authorization flow is not interrupted
+ return session[:user_return_to] if user_is_in_oauth_flow
+
referer_path = URI(request.referer).path unless request.referer.nil?
if from_external_domain? ||
referer_path.eql?(new_user_session_path) ||
@@ -197,4 +203,8 @@ def render_respond_to_format_with_error_message(msg, url_or_path, http_status, e
end
end
end
+
+ def user_is_in_oauth_flow
+ session[:user_return_to].present? && session[:user_return_to].include?('/oauth/authorize')
+ end
end
diff --git a/app/controllers/concerns/plan_permitted_params.rb b/app/controllers/concerns/plan_permitted_params.rb
new file mode 100644
index 0000000000..90363f9de8
--- /dev/null
+++ b/app/controllers/concerns/plan_permitted_params.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+module PlanPermittedParams # rubocop:todo Metrics/ModuleLength, Style/Documentation
+ extend ActiveSupport::Concern
+
+ def plan_permitted_params
+ [
+ :created,
+ :title,
+ :description,
+ :language,
+ :ethical_issues_exist,
+ :ethical_issues_description,
+ :ethical_issues_report,
+ { dmp_ids: identifier_permitted_params },
+ { contact: contributor_permitted_params },
+ { contributors: contributor_permitted_params },
+ { costs: cost_permitted_params },
+ { project: project_permitted_params },
+ { datasets: dataset_permitted_params }
+ ]
+ end
+
+ def identifier_permitted_params
+ %i[
+ type
+ identifier
+ ]
+ end
+
+ def contributor_permitted_params
+ [
+ :firstname,
+ :surname,
+ :mbox,
+ :role,
+ { affiliations: affiliation_permitted_params },
+ { contributor_ids: identifier_permitted_params }
+ ]
+ end
+
+ def affiliation_permitted_params
+ [
+ :name,
+ :abbreviation,
+ { affiliation_ids: identifier_permitted_params }
+ ]
+ end
+
+ def cost_permitted_params
+ %i[
+ title
+ description
+ value
+ currency_code
+ ]
+ end
+
+ def project_permitted_params
+ [
+ :title,
+ :description,
+ :start_on,
+ :end_on,
+ { funding: funding_permitted_params }
+ ]
+ end
+
+ def funding_permitted_params
+ [
+ :name,
+ :funding_status,
+ { funder_ids: identifier_permitted_params },
+ { grant_ids: identifier_permitted_params }
+ ]
+ end
+
+ def dataset_permitted_params
+ [
+ :title,
+ :doi_url,
+ :description,
+ :type,
+ :issued,
+ :language,
+ :personal_data,
+ :sensitive_data,
+ :keywords,
+ :data_quality_assurance,
+ :preservation_statement,
+ { dataset_ids: identifier_permitted_params },
+ { metadata: metadatum_permitted_params },
+ { security_and_privacy_statements: security_and_privacy_statement_permitted_params },
+ { technical_resources: technical_resource_permitted_params },
+ { distributions: distribution_permitted_params }
+ ]
+ end
+
+ def metadatum_permitted_params
+ [
+ :description,
+ :language,
+ { identifier: identifier_permitted_params }
+ ]
+ end
+
+ def security_and_privacy_statement_permitted_params
+ %i[
+ title
+ description
+ ]
+ end
+
+ def technical_resource_permitted_params
+ [
+ :description,
+ { identifier: identifier_permitted_params }
+ ]
+ end
+
+ def distribution_permitted_params
+ [
+ :title,
+ :description,
+ :format,
+ :byte_size,
+ :access_url,
+ :download_url,
+ :data_access,
+ :available_until,
+ { licenses: license_permitted_params },
+ { host: host_permitted_params }
+ ]
+ end
+
+ def license_permitted_params
+ %i[
+ license_ref
+ start_date
+ ]
+ end
+
+ def host_permitted_params
+ [
+ :title,
+ :description,
+ :supports_versioning,
+ :backup_type,
+ :backup_frequency,
+ :storage_type,
+ :availability,
+ :geo_location,
+ :certified_with,
+ :pid_system,
+ { host_ids: identifier_permitted_params }
+ ]
+ end
+end
diff --git a/app/models/oauth_access_grant.rb b/app/models/oauth_access_grant.rb
new file mode 100644
index 0000000000..f39c307af9
--- /dev/null
+++ b/app/models/oauth_access_grant.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: oauth_access_grants
+#
+# id: :integer
+# resource_owner_id: :integer
+# application_id: :integer
+# token: :string
+# expires_in: :integer
+# redirect_uri: :text
+# scopes: :string
+# created_at: :datetime
+# revoked_at: :datetime
+
+class OauthAccessGrant < ApplicationRecord
+end
diff --git a/app/models/oauth_access_token.rb b/app/models/oauth_access_token.rb
new file mode 100644
index 0000000000..b17e824e72
--- /dev/null
+++ b/app/models/oauth_access_token.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: oauth_access_tokens
+#
+# id: :integer
+# resource_owner_id: :integer
+# application_id: :integer
+# token: :string
+# refresh_token: :string
+# expires_in: :integer
+# scopes: :string
+# created_at: :datetime
+# revoked_at: :datetime
+# previous_refresh_token: :string
+
+class OauthAccessToken < ApplicationRecord
+end
diff --git a/app/models/oauth_application.rb b/app/models/oauth_application.rb
new file mode 100644
index 0000000000..4779e53cc4
--- /dev/null
+++ b/app/models/oauth_application.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# == Schema Information
+#
+# Table name: oauth_applications
+#
+# id: :integer
+# name: :string
+# uid: :string
+# secret: :string
+# redirect_uri: :text
+# scopes: :string
+# confidential: :boolean
+# created_at: :datetime
+# updated_at: :datetime
+
+class OauthApplication < ApplicationRecord
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 94d0035eec..8732c2f770 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -98,6 +98,12 @@ class User < ApplicationRecord
has_and_belongs_to_many :notifications, dependent: :destroy,
join_table: 'notification_acknowledgements'
+ has_many :access_grants, class_name: 'Doorkeeper::AccessGrant', foreign_key: :resource_owner_id,
+ dependent: :delete_all
+
+ has_many :access_tokens, class_name: 'Doorkeeper::AccessToken', foreign_key: :resource_owner_id,
+ dependent: :delete_all
+
# ===============
# = Validations =
# ===============
diff --git a/app/policies/api/v2/plans_policy.rb b/app/policies/api/v2/plans_policy.rb
new file mode 100644
index 0000000000..98a22a5025
--- /dev/null
+++ b/app/policies/api/v2/plans_policy.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Security rules for API V2 Plan endpoints
+ class PlansPolicy < ApplicationPolicy
+ # overriding the initializer due to resource owner / user
+ # not needing to be logged in for client app to make requests
+ def initialize(resource_owner, plan = nil) # rubocop:todo Lint/MissingSuper
+ @resource_owner = resource_owner
+ @plan = plan
+ end
+
+ def show?
+ @plan.roles.where(user_id: @resource_owner.id, active: true).exists?
+ end
+
+ class Scope < Scope # rubocop:todo Style/Documentation
+ def initialize(resource_owner) # rubocop:todo Lint/MissingSuper
+ @resource_owner = resource_owner
+ end
+
+ def resolve
+ Plan.joins(:roles)
+ .where(roles: { user_id: @resource_owner.id, active: true })
+ .distinct
+ end
+ end
+ end
+ end
+end
diff --git a/app/policies/api/v2/templates_policy.rb b/app/policies/api/v2/templates_policy.rb
new file mode 100644
index 0000000000..dacdfe48a2
--- /dev/null
+++ b/app/policies/api/v2/templates_policy.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ class TemplatesPolicy < ApplicationPolicy
+ class Scope < Scope # rubocop:todo Style/Documentation
+ def initialize(resource_owner) # rubocop:todo Lint/MissingSuper
+ @resource_owner = resource_owner
+ end
+
+ def resolve
+ # create the sql where clause
+ where_clause = <<-SQL
+ (visibility = 0 AND org_id = ?) OR
+ (visibility = 1 AND customization_of IS NULL)
+ SQL
+
+ # get the templates
+ Template
+ .includes(org: :identifiers)
+ .joins(:org)
+ .published
+ .where(
+ where_clause,
+ @resource_owner.org&.id
+ )
+ .order(:title)
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/api_presenter.rb b/app/presenters/api/v2/api_presenter.rb
new file mode 100644
index 0000000000..1ad7290ab6
--- /dev/null
+++ b/app/presenters/api/v2/api_presenter.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Generic helper methods for API V2
+ class ApiPresenter
+ class << self
+ def boolean_to_yes_no_unknown(value:)
+ return 'unknown' unless value.present?
+
+ value ? 'yes' : 'no'
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/contributor_presenter.rb b/app/presenters/api/v2/contributor_presenter.rb
new file mode 100644
index 0000000000..77232c2838
--- /dev/null
+++ b/app/presenters/api/v2/contributor_presenter.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 contributors views
+ class ContributorPresenter
+ class << self
+ # Convert the specified role into a CRediT Taxonomy URL
+ def role_as_uri(role:)
+ return nil unless role.present?
+ return 'other' if role.to_s.casecmp('other').zero?
+
+ "#{Contributor::ONTOLOGY_BASE_URL}/#{role.to_s.downcase.tr('_', '-')}"
+ end
+
+ def contributor_id(identifiers:)
+ identifiers.find { |id| id.identifier_scheme.name == 'orcid' }
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/funding_presenter.rb b/app/presenters/api/v2/funding_presenter.rb
new file mode 100644
index 0000000000..c878daadb6
--- /dev/null
+++ b/app/presenters/api/v2/funding_presenter.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 funding section
+ class FundingPresenter
+ class << self
+ # If the plan has a grant number then it has been awarded/granted
+ # otherwise it is 'planned'
+ def status(plan:)
+ return 'planned' unless plan.present?
+
+ case plan.funding_status
+ when 'funded'
+ 'granted'
+ when 'denied'
+ 'rejected'
+ else
+ 'planned'
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/language_presenter.rb b/app/presenters/api/v2/language_presenter.rb
new file mode 100644
index 0000000000..1b9d8aebce
--- /dev/null
+++ b/app/presenters/api/v2/language_presenter.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 language values
+ class LanguagePresenter
+ class << self
+ LANGUAGE_MAP = {
+ aa: 'aar', ab: 'abk', af: 'afr', ak: 'aka', am: 'amh', ar: 'ara', an: 'arg',
+ as: 'asm', av: 'ava', ae: 'ave', ay: 'aym', az: 'aze',
+
+ ba: 'bak', bm: 'bam', be: 'bel', bn: 'ben', bh: 'bih', bi: 'bis', bo: 'tib',
+ bs: 'bos', br: 'bre', bg: 'bul',
+
+ ca: 'cat', cs: 'cze', ch: 'cha', ce: 'che', cu: 'chu', cv: 'chv', co: 'cos',
+ cr: 'cre', cy: 'wel',
+
+ da: 'dan', de: 'deu', dv: 'div', dz: 'dzo',
+
+ el: 'gre', en: 'eng', eo: 'epo', es: 'spa', et: 'est', eu: 'baq', ee: 'ewe',
+
+ fo: 'fao', fa: 'per', fj: 'fij', fi: 'fin', fr: 'fre', fy: 'fry', ff: 'ful',
+
+ gd: 'gla', ga: 'gle', gl: 'glg', gv: 'glv', gn: 'grn', gu: 'guj',
+
+ ht: 'hat', ha: 'hau', he: 'heb', hz: 'her', hi: 'hin', ho: 'hmo', hr: 'hrv',
+ hu: 'hun', hy: 'arm',
+
+ ig: 'ibo', io: 'ido', ii: 'iii', iu: 'iku', ie: 'ile', ia: 'ina', id: 'ind',
+ ik: 'ipk', is: 'ice', it: 'ita',
+
+ jv: 'jav', ja: 'jpn',
+
+ kl: 'kal', kn: 'kan', ks: 'kas', kr: 'kau', kk: 'kaz', km: 'khm', ki: 'kik',
+ ky: 'kir', kv: 'kom', kg: 'kon', ko: 'kor', kj: 'kua', ku: 'kur', ka: 'geo',
+ kw: 'cor',
+
+ lo: 'lao', la: 'lat', lv: 'lav', li: 'lim', ln: 'lin', lt: 'lit', lb: 'ltz',
+ lu: 'lub', lg: 'lug',
+
+ mk: 'mac', mh: 'mah', ml: 'mal', mi: 'mao', mr: 'mar', ms: 'may', mg: 'mlg',
+ mt: 'mlt', mn: 'mon', my: 'bur',
+
+ na: 'nau', nv: 'nav', nr: 'nbl', nd: 'nde', ng: 'ndo', ne: 'nep', nl: 'dut',
+ nn: 'nno', nb: 'nob', no: 'nor', ny: 'nya',
+
+ oc: 'oci', oj: 'oji', or: 'ori', om: 'orm', os: 'oss',
+
+ pa: 'pan', pi: 'pli', pl: 'pol', pt: 'por', ps: 'pus',
+
+ qu: 'que',
+
+ rm: 'roh', ro: 'rum', rn: 'run', ru: 'rus', rw: 'kin',
+
+ sg: 'sag', sa: 'san', si: 'sin', sk: 'slo', sl: 'slv', se: 'sme', sm: 'smo',
+ sn: 'sna', sd: 'snd', so: 'som', st: 'sot', sq: 'alb', sc: 'srd', sr: 'srp',
+ ss: 'ssw', su: 'sun', sw: 'swa', sv: 'swe',
+
+ ty: 'tah', ta: 'tam', tt: 'tat', te: 'tel', tg: 'tgk', tl: 'tgl', th: 'tha',
+ ti: 'tir', to: 'ton', tn: 'tsn', ts: 'tso', tk: 'tuk', tr: 'tur', tw: 'twi',
+
+ ug: 'uig', uk: 'ukr', ur: 'urd', uz: 'uzb',
+
+ ve: 'ven', vi: 'vie', vo: 'vol',
+
+ wa: 'wln', wo: 'wol',
+
+ xh: 'xho',
+
+ yi: 'yid', yo: 'yor',
+
+ za: 'zha', zh: 'chi', zu: 'zul'
+ }.freeze
+
+ # Convert the incoming 2 (e.g. en - ISO 639-1) or 2+region (e.g. en-UK)
+ # into the 3 character code (e.g. eng - ISO 639-2)
+ def three_char_code(lang:)
+ two_char_code = lang.to_s.split('-').first
+ LANGUAGE_MAP[two_char_code.to_sym]
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/org_presenter.rb b/app/presenters/api/v2/org_presenter.rb
new file mode 100644
index 0000000000..5302acb7a7
--- /dev/null
+++ b/app/presenters/api/v2/org_presenter.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 affiliation sections
+ class OrgPresenter
+ class << self
+ def affiliation_id(identifiers:)
+ ident = identifiers.find { |id| id.identifier_scheme&.name == 'ror' }
+ return ident if ident.present?
+
+ identifiers.find { |id| id.identifier_scheme&.name == 'fundref' }
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/pagination_presenter.rb b/app/presenters/api/v2/pagination_presenter.rb
new file mode 100644
index 0000000000..1b8fbc1109
--- /dev/null
+++ b/app/presenters/api/v2/pagination_presenter.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for genewric API V2 pagination
+ class PaginationPresenter
+ def initialize(current_url:, per_page:, total_items:, current_page: 1)
+ @url = current_url
+ @per_page = per_page
+ @total_items = total_items
+ @page = current_page
+ end
+
+ def url_without_pagination
+ return nil unless @url.present? && @url.is_a?(String)
+
+ url = @url.gsub(/per_page=\d+/, '')
+ .gsub(/page=\d+/, '')
+ .gsub(/(&)+$/, '').gsub(/\?$/, '')
+
+ (url.include?('?') ? "#{url}&" : "#{url}?")
+ end
+
+ def prev_page?
+ total_pages > 1 && @page != 1
+ end
+
+ def next_page?
+ total_pages > 1 && @page < total_pages
+ end
+
+ def prev_page_link
+ "#{url_without_pagination}page=#{@page - 1}&per_page=#{@per_page}"
+ end
+
+ def next_page_link
+ "#{url_without_pagination}page=#{@page + 1}&per_page=#{@per_page}"
+ end
+
+ private
+
+ def total_pages
+ return 1 unless @total_items.present? && @per_page.present? &&
+ @total_items.positive? && @per_page.positive?
+
+ (@total_items.to_f / @per_page).ceil
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/plan_presenter.rb b/app/presenters/api/v2/plan_presenter.rb
new file mode 100644
index 0000000000..204cd68011
--- /dev/null
+++ b/app/presenters/api/v2/plan_presenter.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 project / DMP
+ class PlanPresenter
+ attr_reader :data_contact, :contributors, :costs
+
+ def initialize(plan:)
+ @contributors = []
+ return unless plan.present?
+
+ @plan = plan
+
+ @data_contact = @plan.owner
+
+ # Attach the first data_curation role as the data_contact, otherwise
+ # add the contributor to the contributors array
+ @plan.contributors.each do |contributor|
+ @data_contact = contributor if contributor.data_curation? && @data_contact.nil?
+ @contributors << contributor
+ end
+
+ @costs = plan_costs(plan: @plan)
+ end
+
+ # Extract the ARK or DOI for the DMP OR use its URL if none exists
+ def identifier
+ doi = @plan.identifiers.select do |id|
+ ::Plan::DMP_ID_TYPES.include?(id.identifier_format)
+ end
+ return doi.first if doi.first.present?
+
+ # if no DOI then use the URL for the API's 'show' method
+ Identifier.new(value: Rails.application.routes.url_helpers.api_v2_plan_url(@plan))
+ end
+
+ private
+
+ # Retrieve the answers that have the Budget theme
+ def plan_costs(plan:)
+ theme = Theme.where(title: 'Cost').first
+ return [] unless theme.present?
+
+ # TODO: define a new 'Currency' question type that includes a float field
+ # any currency type selector (e.g GBP or USD)
+ answers = plan.answers.includes(question: :themes).select do |answer|
+ answer.question.themes.include?(theme)
+ end
+
+ answers.map do |answer|
+ # TODO: Investigate whether question level guidance should be the description
+ { title: answer.question.text, description: nil,
+ currency_code: 'usd', value: answer.text }
+ end
+ end
+ end
+ end
+end
diff --git a/app/presenters/api/v2/research_output_presenter.rb b/app/presenters/api/v2/research_output_presenter.rb
new file mode 100644
index 0000000000..fc44055b2d
--- /dev/null
+++ b/app/presenters/api/v2/research_output_presenter.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper methods for research outputs
+ class ResearchOutputPresenter
+ attr_reader :dataset_id, :preservation_statement, :security_and_privacy, :license_start_date,
+ :data_quality_assurance, :distributions, :metadata, :technical_resources
+
+ def initialize(output:)
+ @research_output = output
+ return unless output.is_a?(ResearchOutput)
+
+ @plan = output.plan
+ @dataset_id = identifier
+
+ load_narrative_content
+
+ @license_start_date = determine_license_start_date(output: output)
+ end
+
+ private
+
+ def identifier
+ Identifier.new(identifiable: @research_output, value: @research_output.id)
+ end
+
+ def determine_license_start_date(output:)
+ return nil unless output.present?
+ return output.release_date.to_formatted_s(:iso8601) if output.release_date.present?
+
+ output.created_at.to_formatted_s(:iso8601)
+ end
+
+ def load_narrative_content
+ @preservation_statement = ''
+ @security_and_privacy = []
+ @data_quality_assurance = ''
+
+ # Disabling rubocop here since a guard clause would make the line too long
+ # rubocop:disable Style/GuardClause
+ if Rails.configuration.x.madmp.extract_preservation_statements_from_themed_questions
+ @preservation_statement = fetch_q_and_a_as_single_statement(themes: %w[Preservation])
+ end
+ if Rails.configuration.x.madmp.extract_security_privacy_statements_from_themed_questions
+ @security_and_privacy = fetch_q_and_a(themes: ['Ethics & privacy', 'Storage & security'])
+ end
+ if Rails.configuration.x.madmp.extract_data_quality_statements_from_themed_questions
+ @data_quality_assurance = fetch_q_and_a_as_single_statement(themes: ['Data Collection'])
+ end
+ # rubocop:enable Style/GuardClause
+ end
+
+ def fetch_q_and_a_as_single_statement(themes:)
+ fetch_q_and_a(themes: themes).collect { |item| item[:description] }.join('
')
+ end
+
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ def fetch_q_and_a(themes:)
+ return [] unless themes.is_a?(Array) && themes.any?
+
+ ret = themes.map do |theme|
+ qs = @plan.questions.select { |q| q.themes.collect(&:title).include?(theme) }
+ descr = qs.map do |q|
+ a = @plan.answers.find { |ans| ans.question_id = q.id }
+ next unless a.present? && !a.blank?
+
+ "Question: #{q.text}
Answer: #{a.text}"
+ end
+ { title: theme, description: descr }
+ end
+ ret.select { |item| item[:description].present? }
+ end
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ end
+ end
+end
diff --git a/app/presenters/api/v2/template_presenter.rb b/app/presenters/api/v2/template_presenter.rb
new file mode 100644
index 0000000000..2d32a45d37
--- /dev/null
+++ b/app/presenters/api/v2/template_presenter.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper class for the API V2 template info
+ class TemplatePresenter
+ def initialize(template:)
+ @template = template
+ end
+
+ # If the plan has a grant number then it has been awarded/granted
+ # otherwise it is 'planned'
+ def title
+ return @template.title unless @template.customization_of.present?
+
+ "#{@template.title} - with additional questions for #{@template.org.name}"
+ end
+ end
+ end
+end
diff --git a/app/services/api/v2/conversion_service.rb b/app/services/api/v2/conversion_service.rb
new file mode 100644
index 0000000000..4a9a932878
--- /dev/null
+++ b/app/services/api/v2/conversion_service.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Api
+ module V2
+ # Helper service that translates to/from the RDA common standard
+ class ConversionService
+ class << self
+ # Converts a boolean field to [yes, no, unknown]
+ def boolean_to_yes_no_unknown(value)
+ return 'yes' if [true, 1].include?(value)
+
+ return 'no' if [false, 0].include?(value)
+
+ 'unknown'
+ end
+
+ # Converts a [yes, no, unknown] field to boolean (or nil)
+ def yes_no_unknown_to_boolean(value)
+ return true if value&.downcase == 'yes'
+
+ return nil if value.blank? || value&.downcase == 'unknown'
+
+ false
+ end
+
+ # Converts the context and value into an Identifier with a psuedo
+ # IdentifierScheme for display in JSON partials. Which will result in:
+ # { type: 'context', identifier: 'value' }
+ def to_identifier(context:, value:)
+ return nil unless value.present? && context.present?
+
+ scheme = IdentifierScheme.new(name: context)
+ Identifier.new(value: value, identifier_scheme: scheme)
+ end
+ end
+ end
+ end
+end
diff --git a/app/views/api/v2/_standard_response.json.jbuilder b/app/views/api/v2/_standard_response.json.jbuilder
new file mode 100644
index 0000000000..372c42cb31
--- /dev/null
+++ b/app/views/api/v2/_standard_response.json.jbuilder
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# locals: response, request, total_items
+
+total_items ||= 0
+
+paginator = Api::V2::PaginationPresenter.new(current_url: request.path,
+ per_page: @per_page,
+ total_items: total_items,
+ current_page: @page)
+
+json.prettify!
+json.ignore_nil!
+
+json.application @application
+json.source "#{request.method} #{request.path}"
+json.time Time.now.to_formatted_s(:iso8601)
+json.caller @caller
+json.code response.status
+json.message Rack::Utils::HTTP_STATUS_CODES[response.status]
+
+if response.status == 200
+
+ # Pagination Links
+ if total_items.positive?
+ json.page @page
+ json.per_page @per_page
+ json.total_items total_items
+
+ # Prepare the base URL by removing the old pagination params
+ json.prev paginator.prev_page_link if paginator.prev_page?
+ json.next paginator.next_page_link if paginator.next_page?
+ else
+ json.total_items 0
+ end
+
+end
diff --git a/app/views/api/v2/contributors/_show.json.jbuilder b/app/views/api/v2/contributors/_show.json.jbuilder
new file mode 100644
index 0000000000..267ff7e3d2
--- /dev/null
+++ b/app/views/api/v2/contributors/_show.json.jbuilder
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+# locals: contributor, is_contact
+
+is_contact ||= false
+
+json.name contributor.is_a?(User) ? contributor.name(false) : contributor.name
+json.mbox contributor.email
+
+if !is_contact && contributor.selected_roles.any?
+ roles = contributor.selected_roles.map do |role|
+ Api::V2::ContributorPresenter.role_as_uri(role: role)
+ end
+ json.role roles if roles.any?
+end
+
+if contributor.org.present?
+ json.affiliation do
+ json.partial! 'api/v2/orgs/show', org: contributor.org
+ end
+end
+
+orcid = contributor.identifier_for_scheme(scheme: 'orcid')
+if orcid.present?
+ id = Api::V2::ContributorPresenter.contributor_id(
+ identifiers: contributor.identifiers
+ )
+ if is_contact
+ json.contact_id do
+ json.partial! 'api/v2/identifiers/show', identifier: id
+ end
+ else
+ json.contributor_id do
+ json.partial! 'api/v2/identifiers/show', identifier: id
+ end
+ end
+end
diff --git a/app/views/api/v2/datasets/_show.json.jbuilder b/app/views/api/v2/datasets/_show.json.jbuilder
new file mode 100644
index 0000000000..1581dff785
--- /dev/null
+++ b/app/views/api/v2/datasets/_show.json.jbuilder
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+# locals: output
+
+if output.is_a?(ResearchOutput)
+ presenter = Api::V2::ResearchOutputPresenter.new(output: output)
+
+ json.type output.output_type
+ json.title output.title
+ json.doi_url output.doi_url
+ json.description output.description
+ json.personal_data Api::V2::ApiPresenter.boolean_to_yes_no_unknown(value: output.personal_data)
+ json.sensitive_data Api::V2::ApiPresenter.boolean_to_yes_no_unknown(value: output.sensitive_data)
+ json.issued output.release_date&.to_formatted_s(:iso8601)
+
+ json.preservation_statement presenter.preservation_statement
+ json.security_and_privacy presenter.security_and_privacy
+ json.data_quality_assurance presenter.data_quality_assurance
+
+ json.dataset_id do
+ json.partial! "api/v2/identifiers/show", identifier: presenter.dataset_id
+ end
+
+ json.distribution output.repositories do |repository|
+ json.title "Anticipated distribution for #{output.title}"
+ json.byte_size output.byte_size
+ json.data_access output.access
+
+ json.host do
+ json.title repository.name
+ json.description repository.description
+ json.url repository.homepage
+
+ # DMPTool extensions to the RDA common metadata standard
+ json.dmproadmap_host_id do
+ json.type "url"
+ json.identifier repository.uri
+ end
+ end
+
+ if output.license.present?
+ json.license [output.license] do |license|
+ json.license_ref license.uri
+ json.start_date presenter.license_start_date
+ end
+ end
+ end
+
+ json.metadata output.metadata_standards do |metadata_standard|
+ website = metadata_standard.locations.find { |loc| loc["type"] == "website" }
+ website = { url: "" } unless website.present?
+
+ descr_array = [metadata_standard.title, metadata_standard.description, website["url"]]
+ json.description descr_array.join(" - ")
+
+ json.metadata_standard_id do
+ json.type "url"
+ json.identifier metadata_standard.uri
+ end
+ end
+
+ json.technical_resource []
+
+ if output.plan.research_domain_id.present?
+ research_domain = ResearchDomain.find_by(id: output.plan.research_domain_id)
+ if research_domain.present?
+ combined = "#{research_domain.identifier} - #{research_domain.label}"
+ json.keyword [research_domain.label, combined]
+ end
+ end
+
+else
+ json.type "dataset"
+ json.title "Generic dataset"
+ json.description "No individual datasets have been defined for this DMP."
+
+ if output.research_domain_id.present?
+ research_domain = ResearchDomain.find_by(id: output.research_domain_id)
+ if research_domain.present?
+ combined = "#{research_domain.identifier} - #{research_domain.label}"
+ json.keyword [research_domain.label, combined]
+ end
+ end
+end
diff --git a/app/views/api/v2/error.json.jbuilder b/app/views/api/v2/error.json.jbuilder
new file mode 100644
index 0000000000..ac08f26d9f
--- /dev/null
+++ b/app/views/api/v2/error.json.jbuilder
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+json.partial! 'api/v2/standard_response'
+
+# json.items []
+json.message @payload[:message]
+json.details @payload[:details]
diff --git a/app/views/api/v2/heartbeat.json.jbuilder b/app/views/api/v2/heartbeat.json.jbuilder
new file mode 100644
index 0000000000..70b165b95d
--- /dev/null
+++ b/app/views/api/v2/heartbeat.json.jbuilder
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+json.partial! 'api/v2/standard_response'
+
+json.items []
diff --git a/app/views/api/v2/identifiers/_show.json.jbuilder b/app/views/api/v2/identifiers/_show.json.jbuilder
new file mode 100644
index 0000000000..c219222aee
--- /dev/null
+++ b/app/views/api/v2/identifiers/_show.json.jbuilder
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+# locals: identifier
+
+json.type identifier&.identifier_format
+json.identifier identifier&.value
diff --git a/app/views/api/v2/me.json.jbuilder b/app/views/api/v2/me.json.jbuilder
new file mode 100644
index 0000000000..18a1e7c114
--- /dev/null
+++ b/app/views/api/v2/me.json.jbuilder
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+json.partial! 'api/v2/standard_response'
+
+if current_user.present?
+ json.items [current_user] do |user|
+ json.name [user.surname, user.firstname].join(', ')
+ json.mbox user.email
+ json.token user.ui_token
+
+ if user.org.present? && ['No funder', 'Non Partner Institution'].exclude?(user.org.name)
+ json.affiliation do
+ json.partial! 'api/v2/orgs/show', org: user.org
+ end
+ end
+
+ orcid = user.identifier_for_scheme(scheme: 'orcid')
+ if orcid.present?
+ json.user_id do
+ json.partial! 'api/v2/identifiers/show', identifier: orcid
+ end
+ end
+ end
+
+else
+ json.items []
+end
diff --git a/app/views/api/v2/orgs/_show.json.jbuilder b/app/views/api/v2/orgs/_show.json.jbuilder
new file mode 100644
index 0000000000..934e429a17
--- /dev/null
+++ b/app/views/api/v2/orgs/_show.json.jbuilder
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+# locals: org
+
+json.name org.name
+json.abbreviation org.abbreviation
+json.region org.region&.abbreviation
+
+if org.identifiers.any?
+ json.affiliation_id do
+ id = Api::V2::OrgPresenter.affiliation_id(identifiers: org.identifiers)
+ json.partial! 'api/v2/identifiers/show', identifier: id
+ end
+end
diff --git a/app/views/api/v2/plans/_cost.json.jbuilder b/app/views/api/v2/plans/_cost.json.jbuilder
new file mode 100644
index 0000000000..ad36e3540e
--- /dev/null
+++ b/app/views/api/v2/plans/_cost.json.jbuilder
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+# locals: cost
+
+json.title cost[:title]
+json.description cost[:description]
+json.currency_code cost[:currency_code]
+json.value cost[:value]
diff --git a/app/views/api/v2/plans/_funding.json.jbuilder b/app/views/api/v2/plans/_funding.json.jbuilder
new file mode 100644
index 0000000000..35786ac2dc
--- /dev/null
+++ b/app/views/api/v2/plans/_funding.json.jbuilder
@@ -0,0 +1,41 @@
+# frozen_string_literal: true
+
+# locals: plan
+
+json.name plan.funder&.name
+
+if plan.funder.present?
+ id = Api::V2::OrgPresenter.affiliation_id(identifiers: plan.funder.identifiers)
+
+ if id.present?
+ json.funder_id do
+ json.partial! 'api/v2/identifiers/show', identifier: id
+ end
+ end
+end
+
+if plan.grant_id.present? && plan.grant.present?
+ json.grant_id do
+ json.partial! 'api/v2/identifiers/show', identifier: plan.grant
+ end
+end
+
+json.funding_status Api::V2::FundingPresenter.status(plan: plan)
+
+# DMPTool extensions to the RDA common metadata standard
+# ------------------------------------------------------
+
+# We collect a user entered ID on the form, so this is a way to convey it to other systems
+# The ID would typically be something relevant to the funder or research organization
+if plan.identifier.present?
+ json.dmproadmap_funding_opportunity_id do
+ json.partial! 'api/v2/identifiers/show', identifier: Identifier.new(identifiable: plan,
+ value: plan.identifier)
+ end
+end
+
+# Since the Plan owner (aka contact) and contributor orgs could be different than the
+# one associated with the Plan, we add it here.
+json.dmproadmap_funded_affiliations [plan.org] do |funded_org|
+ json.partial! 'api/v2/orgs/show', org: funded_org
+end
diff --git a/app/views/api/v2/plans/_project.json.jbuilder b/app/views/api/v2/plans/_project.json.jbuilder
new file mode 100644
index 0000000000..9bf3f97fea
--- /dev/null
+++ b/app/views/api/v2/plans/_project.json.jbuilder
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# locals: plan
+
+json.title plan.title
+json.description plan.description
+
+start_date = plan.start_date || Time.now
+json.start start_date.to_formatted_s(:iso8601)
+
+end_date = plan.end_date || (Time.now + 2.years)
+json.end end_date&.to_formatted_s(:iso8601)
+
+if plan.funder.present? || plan.grant_id.present?
+ json.funding [plan] do
+ json.partial! 'api/v2/plans/funding', plan: plan
+ end
+end
diff --git a/app/views/api/v2/plans/_show.json.jbuilder b/app/views/api/v2/plans/_show.json.jbuilder
new file mode 100644
index 0000000000..0de52775a3
--- /dev/null
+++ b/app/views/api/v2/plans/_show.json.jbuilder
@@ -0,0 +1,72 @@
+# frozen_string_literal: true
+
+# locals: plan
+
+json.schema 'https://github.com/RDA-DMP-Common/RDA-DMP-Common-Standard/tree/master/examples/JSON/JSON-schema/1.0'
+
+presenter = Api::V2::PlanPresenter.new(plan: plan)
+
+# Note the symbol of the dmproadmap json object
+# nested in extensions which is the container for the json template object, etc.
+
+# A JSON representation of a Data Management Plan in the
+# RDA Common Standard format
+json.title plan.title
+json.description plan.description
+json.language Api::V2::LanguagePresenter.three_char_code(
+ lang: LocaleService.default_locale
+)
+json.created plan.created_at.to_formatted_s(:iso8601)
+json.modified plan.updated_at.to_formatted_s(:iso8601)
+
+json.ethical_issues_exist Api::V2::ConversionService.boolean_to_yes_no_unknown(plan.ethical_issues)
+json.ethical_issues_description plan.ethical_issues_description
+json.ethical_issues_report plan.ethical_issues_report
+
+id = presenter.identifier
+if id.present?
+ json.dmp_id do
+ json.partial! 'api/v2/identifiers/show', identifier: id
+ end
+end
+
+if presenter.data_contact.present?
+ json.contact do
+ json.partial! 'api/v2/contributors/show', contributor: presenter.data_contact,
+ is_contact: true
+ end
+end
+
+unless @minimal
+ if presenter.contributors.any?
+ json.contributor presenter.contributors do |contributor|
+ json.partial! 'api/v2/contributors/show', contributor: contributor,
+ is_contact: false
+ end
+ end
+
+ if presenter.costs.any?
+ json.cost presenter.costs do |cost|
+ json.partial! 'api/v2/plans/cost', cost: cost
+ end
+ end
+
+ json.project [plan] do |pln|
+ json.partial! 'api/v2/plans/project', plan: pln
+ end
+
+ outputs = plan.research_outputs.any? ? plan.research_outputs : [plan]
+
+ json.dataset outputs do |output|
+ json.partial! "api/v2/datasets/show", output: output
+ end
+
+ json.extension [plan.template] do |template|
+ json.set! :dmproadmap do
+ json.template do
+ json.id template.id
+ json.title template.title
+ end
+ end
+ end
+end
diff --git a/app/views/api/v2/plans/index.json.jbuilder b/app/views/api/v2/plans/index.json.jbuilder
new file mode 100644
index 0000000000..f19f41d1dc
--- /dev/null
+++ b/app/views/api/v2/plans/index.json.jbuilder
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+json.partial! 'api/v2/standard_response', total_items: @total_items
+
+json.items @items do |item|
+ json.dmp do
+ json.partial! 'api/v2/plans/show', plan: item
+ end
+end
diff --git a/app/views/api/v2/templates/index.json.jbuilder b/app/views/api/v2/templates/index.json.jbuilder
new file mode 100644
index 0000000000..cdb35f54fb
--- /dev/null
+++ b/app/views/api/v2/templates/index.json.jbuilder
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+json.partial! 'api/v2/standard_response', total_items: @total_items
+
+json.items @items do |template|
+ presenter = Api::V2::TemplatePresenter.new(template: template)
+
+ json.dmp_template do
+ json.title presenter.title
+ json.description template.description
+ json.version template.version
+ json.created template.created_at.to_formatted_s(:iso8601)
+ json.modified template.updated_at.to_formatted_s(:iso8601)
+
+ json.affiliation do
+ json.partial! 'api/v2/orgs/show', org: template.org
+ end
+
+ json.template_id do
+ identifier = Api::V2::ConversionService.to_identifier(context: @application,
+ value: template.id)
+ json.partial! 'api/v2/identifiers/show', identifier: identifier
+ end
+ end
+end
diff --git a/app/views/doorkeeper/applications/_delete_form.html.erb b/app/views/doorkeeper/applications/_delete_form.html.erb
new file mode 100644
index 0000000000..b6377d35f5
--- /dev/null
+++ b/app/views/doorkeeper/applications/_delete_form.html.erb
@@ -0,0 +1,6 @@
+<%- submit_btn_css ||= 'btn btn-link' %>
+<%= form_tag oauth_application_path(application), method: :delete do %>
+ <%= submit_tag t('doorkeeper.applications.buttons.destroy'),
+ onclick: "return confirm('#{ t('doorkeeper.applications.confirmations.destroy') }')",
+ class: submit_btn_css, style: "border-radius: 0;" %>
+<% end %>
\ No newline at end of file
diff --git a/app/views/doorkeeper/applications/_form.html.erb b/app/views/doorkeeper/applications/_form.html.erb
new file mode 100644
index 0000000000..5aeeeaf9ab
--- /dev/null
+++ b/app/views/doorkeeper/applications/_form.html.erb
@@ -0,0 +1,42 @@
+<%= form_for application, url: doorkeeper_submit_path(application), as: :doorkeeper_application, html: { role: 'form' } do |f| %>
+ <% if application.errors.any? %>
+
<%= t('doorkeeper.applications.form.error') %>
<%= link_to t('.new'), new_oauth_application_path, class: 'btn btn-secondary', style: "border-radius: 0;" %>
+ +| <%= t('.name') %> | +<%= t('.callback_url') %> | +<%= t('.actions') %> | ++ |
|---|---|---|---|
| + <%= link_to application.name, oauth_application_path(application) %> + | ++ <%= simple_format(application.redirect_uri) %> + | ++ <%= link_to t('doorkeeper.applications.buttons.edit'), edit_oauth_application_path(application), class: 'btn btn-link' %> + | ++ <%= render 'delete_form', application: application %> + | +
+ <%= (local_assigns[:error_response] ? error_response : @pre_auth.error_response).body[:error_description] %> ++
+ <%= raw t('.prompt', client_name: content_tag(:strong, class: 'text-info') { @pre_auth.client.name }) %> +
+ + <% if @pre_auth.scopes.count > 0 %> +<%= t('.able_to') %>:
+ +<%= params[:code] %>
+| <%= t('doorkeeper.authorized_applications.index.application') %> | +<%= t('doorkeeper.authorized_applications.index.created_at') %> | ++ |
|---|---|---|
| <%= application.name %> | +<%= application.created_at.strftime(t('doorkeeper.authorized_applications.index.date_format')) %> | +<%= render 'delete_form', application: application %> | +