Skip to content

Commit 6c67b0f

Browse files
committed
feat: let users save and remove their OpenTheso API key from their account page
1 parent a696c31 commit 6c67b0f

7 files changed

Lines changed: 86 additions & 24 deletions

File tree

app/assets/stylesheets/account.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@
162162
border: 1px solid var(--primary-color);
163163
text-decoration: none;
164164
color: var(--primary-color);
165+
background: none;
166+
cursor: pointer;
165167
border-radius: 5px;
166168
padding: 12px 20px;
167169
font-size: 14px;

app/controllers/users_controller.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -175,18 +175,18 @@ def update_external_tools
175175
tool_name = params.dig(:user, :tool_name)
176176
apikey = params.dig(:user, :apikey)
177177

178-
if tool_name.blank? || apikey.blank?
178+
if tool_name.blank?
179179
flash[:notice] = t('users.external_tools_apikey_required')
180180
else
181181
external_tools = Array(@user.externalTools).reject { |tool| tool.toolName.eql?(tool_name) }
182182
.map { |tool| { toolName: tool.toolName, apikey: tool.apikey } }
183-
external_tools << { toolName: tool_name, apikey: apikey }
183+
external_tools << { toolName: tool_name, apikey: apikey } if apikey.present?
184184
error_response = @user.update(values: { externalTools: external_tools })
185185

186186
if response_error?(error_response)
187187
flash[:notice] = t('users.error_saving_external_tools')
188188
else
189-
flash[:notice] = t('users.external_tools_saved')
189+
flash[:notice] = apikey.present? ? t('users.external_tools_saved') : t('users.external_tools_removed')
190190
end
191191
end
192192
redirect_to user_path(@user.username)

app/helpers/users_helper.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
module UsersHelper
22
def external_tools_list
33
tools = Rails.cache.fetch('external_tools_list', expires_in: 1.hour) do
4-
Array(LinkedData::Client::HTTP.get('/external_tools')).map do |tool|
5-
{ name: tool.name, label: tool.title, url: tool.homepage.to_s }
6-
end
4+
Array(LinkedData::Client::HTTP.get('/external_tools'))
5+
.sort_by { |tool| tool.created.to_s }
6+
.map { |tool| { name: tool.name, label: tool.title, url: tool.homepage.to_s } }
77
end
88
tools || []
99
end
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
module ExternalTools
2+
# Connection recipe for OpenTheso instances:
3+
# 1. resolve the portal acronym to the thesaurus id (persistent name)
4+
# 2. request a temporary SSO token with the user API key
5+
class OpenthesoConnector
6+
7+
def initialize(tool)
8+
@tool = tool
9+
@base_url = tool[:url].to_s.chomp('/')
10+
end
11+
12+
def connect(api_key:, acronym:, concept_id: nil)
13+
idt = resolve_thesaurus(acronym)
14+
if idt.blank?
15+
return { error: I18n.t('ontologies.external_tool_resolve_failed', tool: @tool[:label]), status: :not_found }
16+
end
17+
18+
data = request_sso_token(api_key, idt, concept_id)
19+
if data && data['redirectUrl']
20+
{ redirect_url: "#{@base_url}#{data['redirectUrl']}" }
21+
else
22+
{ error: error_message(data), status: :unprocessable_entity }
23+
end
24+
end
25+
26+
private
27+
28+
def error_message(data)
29+
case data && data['errorCode']
30+
when 'API_KEY_INVALID', 'API_KEY_MISSING'
31+
I18n.t('ontologies.external_tool_invalid_apikey', tool: @tool[:label])
32+
else
33+
I18n.t('ontologies.external_tool_connection_failed', tool: @tool[:label])
34+
end
35+
end
36+
37+
def resolve_thesaurus(acronym)
38+
uri = URI("#{@base_url}/openapi/v1/thesaurus-resolve/#{acronym.to_s.downcase}")
39+
response = Net::HTTP.get_response(uri)
40+
return nil unless response.is_a?(Net::HTTPSuccess)
41+
42+
JSON.parse(response.body)['idTheso']
43+
end
44+
45+
def request_sso_token(api_key, idt, idc)
46+
uri = URI("#{@base_url}/api/v2/auth/token")
47+
http = Net::HTTP.new(uri.host, uri.port)
48+
http.use_ssl = uri.scheme == 'https'
49+
50+
request = Net::HTTP::Post.new(uri)
51+
request['X-API-Key'] = api_key
52+
request['Content-Type'] = 'application/json'
53+
request.body = { idc: idc.to_s, idt: idt }.to_json
54+
55+
response = http.request(request)
56+
JSON.parse(response.body)
57+
end
58+
end
59+
end

app/views/users/show.html.haml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -58,24 +58,19 @@
5858
= t('users.show.api_documentation')
5959
= render partial: "shared/svgs/external_link_icon"
6060
.account-page-card{'data-controller': 'reveal-component'}
61-
.account-page-card-container
62-
.account-page-card-sub-container
63-
%h4.account-page-card-title= t('users.show.external_tools')
64-
%p.account-page-card-desc= t('users.show.external_tools_description')
65-
- external_tools_list.each do |tool|
66-
- saved_key = user_external_tool_apikey(@user, tool[:name])
67-
.account-page-subscription
68-
%a{'data-action': 'click->reveal-component#toggle', 'data-id': "external_tool_form_#{tool[:name]}", style: 'cursor: pointer;'}
69-
= tool[:label]
70-
%div
71-
- if saved_key.present?
72-
= render ChipButtonComponent.new(text: t('users.show.external_tool_connected'), type: 'static')
73-
.d-none{id: "external_tool_form_#{tool[:name]}"}
74-
= form_tag user_external_tools_path(url_encode(@user.username)) do
75-
= hidden_field_tag 'user[tool_name]', tool[:name]
76-
= text_field_tag 'user[apikey]', saved_key, class: 'register-input-long', placeholder: t('users.show.external_tool_apikey_placeholder')
77-
.mt-2
78-
= render Buttons::RegularButtonComponent.new(id: "save_#{tool[:name]}_apikey", variant: 'secondary', size: 'slim', type: 'submit', value: t('users.show.external_tool_apikey_save'))
61+
%h4.account-page-card-title= t('users.show.external_tools')
62+
%p.account-page-card-desc= t('users.show.external_tools_description')
63+
- external_tools_list.each do |tool|
64+
- saved_key = user_external_tool_apikey(@user, tool[:name])
65+
.account-page-subscription
66+
%a{'data-action': 'click->reveal-component#toggle', 'data-id': "external_tool_form_#{tool[:name]}", style: 'cursor: pointer;'}
67+
= tool[:label]
68+
.d-none.mt-3{id: "external_tool_form_#{tool[:name]}"}
69+
= form_tag user_external_tools_path(url_encode(@user.username)) do
70+
= hidden_field_tag 'user[tool_name]', tool[:name]
71+
= render Input::TextInputComponent.new(name: 'user[apikey]', value: saved_key, placeholder: t('users.show.external_tool_apikey_placeholder'))
72+
.mt-3
73+
= render Buttons::RegularButtonComponent.new(id: "save_#{tool[:name]}_apikey", variant: 'secondary', size: 'slim', type: 'submit', value: t('users.show.external_tool_apikey_save'))
7974
.account-page-card{'data-controller': 'reveal-component'}
8075
%h4.account-page-card-title
8176
= t('users.show.custom_semantic_resource')

config/locales/en.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,8 @@ en:
956956
external_tool_no_apikey: Please save your %{tool} API key in your account first.
957957
external_tool_go_to_account: Go to my account
958958
external_tool_resolve_failed: No matching %{tool} thesaurus found for this ontology.
959+
external_tool_invalid_apikey: Your %{tool} API key seems invalid. Please check it in your account.
960+
external_tool_connection_failed: Unable to connect to %{tool}, please try again later.
959961
confirm_submission_title: Confirm submission
960962
confirm_submission_message: Are you sure you want to submit this ontology? This action will create a new submission.
961963
admin_links: Administration links
@@ -1740,6 +1742,7 @@ en:
17401742
error_saving_external_tools: Error saving the API key, please try again
17411743
external_tools_apikey_required: Please enter an API key before saving
17421744
external_tools_saved: API key saved successfully
1745+
external_tools_removed: API key removed successfully
17431746
error_subscribe: Something went wrong ...
17441747
first_name_required: First name field is required
17451748
index:

config/locales/fr.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,8 @@ fr:
988988
external_tool_no_apikey: Veuillez d'abord enregistrer votre clé API %{tool} dans votre compte.
989989
external_tool_go_to_account: Aller à mon compte
990990
external_tool_resolve_failed: Aucun thésaurus %{tool} ne correspond à cette ontologie.
991+
external_tool_invalid_apikey: Votre clé API %{tool} semble invalide. Veuillez la vérifier dans votre compte.
992+
external_tool_connection_failed: Impossible de se connecter à %{tool}, veuillez réessayer plus tard.
991993
confirm_submission_title: Confirmer la soumission
992994
confirm_submission_message: Êtes-vous sûr de vouloir soumettre cette ontologie ? Cette action créera une nouvelle soumission.
993995
admin_links: Liens d'administratiion
@@ -1795,6 +1797,7 @@ fr:
17951797
error_subscribe: Quelque chose s'est mal passé ...
17961798
external_tools_apikey_required: Veuillez saisir une clé API avant de sauvegarder
17971799
external_tools_saved: Clé API enregistrée avec succès
1800+
external_tools_removed: Clé API supprimée avec succès
17981801
first_name_required: Le champ Prénom est requis
17991802
index:
18001803
actions: Actions

0 commit comments

Comments
 (0)