iatoolkit 0.11.0__py3-none-any.whl → 0.71.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. iatoolkit/__init__.py +2 -6
  2. iatoolkit/base_company.py +9 -29
  3. iatoolkit/cli_commands.py +1 -1
  4. iatoolkit/common/routes.py +96 -52
  5. iatoolkit/common/session_manager.py +2 -1
  6. iatoolkit/common/util.py +17 -27
  7. iatoolkit/company_registry.py +1 -2
  8. iatoolkit/iatoolkit.py +97 -53
  9. iatoolkit/infra/llm_client.py +15 -20
  10. iatoolkit/infra/llm_proxy.py +38 -10
  11. iatoolkit/infra/openai_adapter.py +1 -1
  12. iatoolkit/infra/redis_session_manager.py +48 -2
  13. iatoolkit/locales/en.yaml +167 -0
  14. iatoolkit/locales/es.yaml +163 -0
  15. iatoolkit/repositories/database_manager.py +23 -3
  16. iatoolkit/repositories/document_repo.py +1 -1
  17. iatoolkit/repositories/models.py +35 -10
  18. iatoolkit/repositories/profile_repo.py +3 -2
  19. iatoolkit/repositories/vs_repo.py +26 -20
  20. iatoolkit/services/auth_service.py +193 -0
  21. iatoolkit/services/branding_service.py +70 -25
  22. iatoolkit/services/company_context_service.py +155 -0
  23. iatoolkit/services/configuration_service.py +133 -0
  24. iatoolkit/services/dispatcher_service.py +80 -105
  25. iatoolkit/services/document_service.py +5 -2
  26. iatoolkit/services/embedding_service.py +146 -0
  27. iatoolkit/services/excel_service.py +30 -26
  28. iatoolkit/services/file_processor_service.py +4 -12
  29. iatoolkit/services/history_service.py +7 -16
  30. iatoolkit/services/i18n_service.py +104 -0
  31. iatoolkit/services/jwt_service.py +18 -29
  32. iatoolkit/services/language_service.py +83 -0
  33. iatoolkit/services/load_documents_service.py +100 -113
  34. iatoolkit/services/mail_service.py +9 -4
  35. iatoolkit/services/profile_service.py +152 -76
  36. iatoolkit/services/prompt_manager_service.py +20 -16
  37. iatoolkit/services/query_service.py +208 -96
  38. iatoolkit/services/search_service.py +11 -4
  39. iatoolkit/services/sql_service.py +57 -25
  40. iatoolkit/services/tasks_service.py +1 -1
  41. iatoolkit/services/user_feedback_service.py +72 -34
  42. iatoolkit/services/user_session_context_service.py +112 -54
  43. iatoolkit/static/images/fernando.jpeg +0 -0
  44. iatoolkit/static/js/chat_feedback_button.js +80 -0
  45. iatoolkit/static/js/chat_help_content.js +124 -0
  46. iatoolkit/static/js/chat_history_button.js +110 -0
  47. iatoolkit/static/js/chat_logout_button.js +36 -0
  48. iatoolkit/static/js/chat_main.js +135 -222
  49. iatoolkit/static/js/chat_onboarding_button.js +103 -0
  50. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  51. iatoolkit/static/js/chat_reload_button.js +35 -0
  52. iatoolkit/static/styles/chat_iatoolkit.css +289 -210
  53. iatoolkit/static/styles/chat_modal.css +63 -77
  54. iatoolkit/static/styles/chat_public.css +107 -0
  55. iatoolkit/static/styles/landing_page.css +182 -0
  56. iatoolkit/static/styles/onboarding.css +176 -0
  57. iatoolkit/system_prompts/query_main.prompt +5 -22
  58. iatoolkit/templates/_company_header.html +20 -0
  59. iatoolkit/templates/_login_widget.html +42 -0
  60. iatoolkit/templates/base.html +40 -20
  61. iatoolkit/templates/change_password.html +57 -36
  62. iatoolkit/templates/chat.html +180 -86
  63. iatoolkit/templates/chat_modals.html +138 -68
  64. iatoolkit/templates/error.html +44 -8
  65. iatoolkit/templates/forgot_password.html +40 -23
  66. iatoolkit/templates/index.html +145 -0
  67. iatoolkit/templates/login_simulation.html +45 -0
  68. iatoolkit/templates/onboarding_shell.html +107 -0
  69. iatoolkit/templates/signup.html +63 -65
  70. iatoolkit/views/base_login_view.py +91 -0
  71. iatoolkit/views/change_password_view.py +56 -31
  72. iatoolkit/views/embedding_api_view.py +65 -0
  73. iatoolkit/views/external_login_view.py +61 -28
  74. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +10 -3
  75. iatoolkit/views/forgot_password_view.py +27 -21
  76. iatoolkit/views/help_content_api_view.py +54 -0
  77. iatoolkit/views/history_api_view.py +56 -0
  78. iatoolkit/views/home_view.py +50 -23
  79. iatoolkit/views/index_view.py +14 -0
  80. iatoolkit/views/init_context_api_view.py +74 -0
  81. iatoolkit/views/llmquery_api_view.py +58 -0
  82. iatoolkit/views/login_simulation_view.py +93 -0
  83. iatoolkit/views/login_view.py +130 -37
  84. iatoolkit/views/logout_api_view.py +49 -0
  85. iatoolkit/views/profile_api_view.py +46 -0
  86. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +10 -10
  87. iatoolkit/views/signup_view.py +41 -36
  88. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  89. iatoolkit/views/tasks_review_api_view.py +55 -0
  90. iatoolkit/views/user_feedback_api_view.py +60 -0
  91. iatoolkit/views/verify_user_view.py +34 -29
  92. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/METADATA +41 -23
  93. iatoolkit-0.71.2.dist-info/RECORD +122 -0
  94. iatoolkit-0.71.2.dist-info/licenses/LICENSE +21 -0
  95. iatoolkit/common/auth.py +0 -200
  96. iatoolkit/static/images/arrow_up.png +0 -0
  97. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  98. iatoolkit/static/images/logo_clinica.png +0 -0
  99. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  100. iatoolkit/static/images/logo_maxxa.png +0 -0
  101. iatoolkit/static/images/logo_notaria.png +0 -0
  102. iatoolkit/static/images/logo_tarjeta.png +0 -0
  103. iatoolkit/static/images/logo_umayor.png +0 -0
  104. iatoolkit/static/images/upload.png +0 -0
  105. iatoolkit/static/js/chat_feedback.js +0 -115
  106. iatoolkit/static/js/chat_history.js +0 -117
  107. iatoolkit/static/styles/chat_info.css +0 -53
  108. iatoolkit/templates/header.html +0 -31
  109. iatoolkit/templates/home.html +0 -199
  110. iatoolkit/templates/login.html +0 -43
  111. iatoolkit/templates/test.html +0 -9
  112. iatoolkit/views/chat_token_request_view.py +0 -98
  113. iatoolkit/views/chat_view.py +0 -58
  114. iatoolkit/views/download_file_view.py +0 -58
  115. iatoolkit/views/external_chat_login_view.py +0 -95
  116. iatoolkit/views/history_view.py +0 -57
  117. iatoolkit/views/llmquery_view.py +0 -65
  118. iatoolkit/views/tasks_review_view.py +0 -83
  119. iatoolkit/views/user_feedback_view.py +0 -74
  120. iatoolkit-0.11.0.dist-info/RECORD +0 -110
  121. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/WHEEL +0 -0
  122. {iatoolkit-0.11.0.dist-info → iatoolkit-0.71.2.dist-info}/top_level.txt +0 -0
@@ -4,57 +4,150 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from flask.views import MethodView
7
- from flask import request, redirect, render_template, url_for
7
+ from flask import (request, redirect, render_template, url_for,
8
+ render_template_string, flash)
8
9
  from injector import inject
9
10
  from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.services.jwt_service import JWTService
12
+ from iatoolkit.services.query_service import QueryService
13
+ from iatoolkit.services.prompt_manager_service import PromptService
14
+ from iatoolkit.services.branding_service import BrandingService
15
+ from iatoolkit.services.configuration_service import ConfigurationService
16
+ from iatoolkit.services.i18n_service import I18nService
17
+ from iatoolkit.views.base_login_view import BaseLoginView
18
+ import logging
10
19
 
11
- class LoginView(MethodView):
12
- @inject
13
- def __init__(self, profile_service: ProfileService):
14
- self.profile_service = profile_service
15
-
16
- def get(self, company_short_name: str):
17
- # get company info
18
- company = self.profile_service.get_company_by_short_name(company_short_name)
19
- if not company:
20
- return render_template('error.html', message="Empresa no encontrada"), 404
21
-
22
- return render_template('login.html',
23
- company=company,
24
- company_short_name=company_short_name)
25
20
 
21
+ class LoginView(BaseLoginView):
22
+ """
23
+ Handles login for local users.
24
+ Authenticates and then delegates the path decision (fast/slow) to the base class.
25
+ """
26
26
  def post(self, company_short_name: str):
27
- # get company info
28
27
  company = self.profile_service.get_company_by_short_name(company_short_name)
29
28
  if not company:
30
- return render_template('error.html', message="Empresa no encontrada"), 404
29
+ return render_template('error.html',
30
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
31
31
 
32
+ branding_data = self.branding_service.get_company_branding(company_short_name)
32
33
  email = request.form.get('email')
34
+ password = request.form.get('password')
35
+
36
+ # 1. Authenticate internal user
37
+ auth_response = self.auth_service.login_local_user(
38
+ company_short_name=company_short_name,
39
+ email=email,
40
+ password=password
41
+ )
42
+
43
+ if not auth_response['success']:
44
+ flash(auth_response["message"], 'error')
45
+ home_template = self.utility.get_company_template(company_short_name, "home.html")
46
+
47
+ return render_template_string(
48
+ home_template,
49
+ company_short_name=company_short_name,
50
+ company=company,
51
+ branding=branding_data,
52
+ form_data={"email": email},
53
+ ), 400
54
+
55
+ user_identifier = auth_response['user_identifier']
56
+
57
+ # 3. define URL to call when slow path is finished
58
+ target_url = url_for('finalize_no_token',
59
+ company_short_name=company_short_name,
60
+ _external=True)
61
+
62
+ # 2. Delegate the path decision to the centralized logic.
33
63
  try:
34
- password = request.form.get('password')
64
+ return self._handle_login_path(company_short_name, user_identifier, target_url)
65
+ except Exception as e:
66
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
67
+ return render_template(
68
+ "error.html",
69
+ company_short_name=company_short_name,
70
+ branding=branding_data,
71
+ message=message
72
+ ), 500
73
+
74
+
75
+ class FinalizeContextView(MethodView):
76
+ """
77
+ Finalizes context loading in the slow path.
78
+ This view is invoked by the iframe inside onboarding_shell.html.
79
+ """
80
+ @inject
81
+ def __init__(self,
82
+ profile_service: ProfileService,
83
+ query_service: QueryService,
84
+ prompt_service: PromptService,
85
+ branding_service: BrandingService,
86
+ config_service: ConfigurationService,
87
+ jwt_service: JWTService,
88
+ i18n_service: I18nService
89
+ ):
90
+ self.profile_service = profile_service
91
+ self.jwt_service = jwt_service
92
+ self.query_service = query_service
93
+ self.prompt_service = prompt_service
94
+ self.branding_service = branding_service
95
+ self.config_service = config_service
96
+ self.i18n_service = i18n_service
97
+
98
+ def get(self, company_short_name: str, token: str = None):
99
+ try:
100
+ session_info = self.profile_service.get_current_session_info()
101
+ if session_info:
102
+ # session exists, internal user
103
+ user_identifier = session_info.get('user_identifier')
104
+ token = ''
105
+ elif token:
106
+ # user identified by api-key
107
+ payload = self.jwt_service.validate_chat_jwt(token)
108
+ if not payload:
109
+ logging.warning("Fallo crítico: No se pudo leer el auth token.")
110
+ return redirect(url_for('home', company_short_name=company_short_name))
111
+
112
+ user_identifier = payload.get('user_identifier')
113
+ else:
114
+ logging.error("missing session information or auth token")
115
+ return redirect(url_for('home', company_short_name=company_short_name))
116
+
117
+ company = self.profile_service.get_company_by_short_name(company_short_name)
118
+ if not company:
119
+ return render_template('error.html',
120
+ company_short_name=company_short_name,
121
+ message="Empresa no encontrada"), 404
122
+ branding_data = self.branding_service.get_company_branding(company_short_name)
123
+
124
+ # 2. Finalize the context rebuild (the heavy task).
125
+ self.query_service.set_context_for_llm(
126
+ company_short_name=company_short_name,
127
+ user_identifier=user_identifier
128
+ )
129
+
130
+ # 3. render the chat page.
131
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
132
+ onboarding_cards = self.config_service.get_configuration(company_short_name, 'onboarding_cards')
133
+
134
+ # Get the entire 'js_messages' block in the correct language.
135
+ js_translations = self.i18n_service.get_translation_block('js_messages')
35
136
 
36
- response = self.profile_service.login(
137
+ return render_template(
138
+ "chat.html",
37
139
  company_short_name=company_short_name,
38
- email=email,
39
- password=password
40
- )
41
-
42
- if "error" in response:
43
- return render_template(
44
- 'login.html',
45
- company_short_name=company_short_name,
46
- company=company,
47
- form_data={
48
- "email": email,
49
- "password": password,
50
- },
51
- alert_message=response["error"]), 400
52
-
53
- return redirect(url_for('chat', company_short_name=company_short_name))
140
+ user_identifier=user_identifier,
141
+ branding=branding_data,
142
+ prompts=prompts,
143
+ onboarding_cards=onboarding_cards,
144
+ js_translations=js_translations,
145
+ redeem_token=token
146
+ )
54
147
 
55
148
  except Exception as e:
56
149
  return render_template("error.html",
57
- company=company,
58
150
  company_short_name=company_short_name,
59
- message="Ha ocurrido un error inesperado."), 500
151
+ branding=branding_data,
152
+ message=f"An unexpected error occurred during context loading: {str(e)}"), 500
60
153
 
@@ -0,0 +1,49 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask.views import MethodView
7
+ from flask import redirect, url_for, jsonify
8
+ from injector import inject
9
+ from iatoolkit.services.auth_service import AuthService
10
+ from iatoolkit.services.profile_service import ProfileService
11
+ from iatoolkit.common.session_manager import SessionManager
12
+ import logging
13
+
14
+ class LogoutApiView(MethodView):
15
+ @inject
16
+ def __init__(self,
17
+ profile_service: ProfileService,
18
+ auth_service: AuthService):
19
+ self.profile_service = profile_service
20
+ self.auth_service = auth_service
21
+
22
+ def get(self, company_short_name: str = None):
23
+ try:
24
+ # 1. Get the authenticated user's
25
+ auth_result = self.auth_service.verify(anonymous=True)
26
+ if not auth_result.get("success"):
27
+ return jsonify(auth_result), auth_result.get("status_code", 401)
28
+
29
+ company = self.profile_service.get_company_by_short_name(company_short_name)
30
+ if not company:
31
+ return jsonify({"error": "company not found."}), 404
32
+
33
+ # get URL for redirection
34
+ url_for_redirect = company.parameters.get('external_urls', {}).get('logout_url')
35
+ if not url_for_redirect:
36
+ url_for_redirect = url_for('home', company_short_name=company_short_name)
37
+
38
+ # clear de session cookie
39
+ SessionManager.clear()
40
+
41
+ return {
42
+ 'status': 'success',
43
+ 'url': url_for_redirect,
44
+ }, 200
45
+ except Exception as e:
46
+ logging.exception(f"Unexpected error: {e}")
47
+ return {'status': 'error'}, 500
48
+
49
+
@@ -0,0 +1,46 @@
1
+ # iatoolkit/views/profile_api_view.py
2
+ from flask import request, jsonify
3
+ from flask.views import MethodView
4
+ from injector import inject
5
+ from iatoolkit.services.auth_service import AuthService
6
+ from iatoolkit.services.profile_service import ProfileService
7
+
8
+
9
+ class UserLanguageApiView(MethodView):
10
+ """
11
+ API endpoint for managing user language preferences.
12
+ """
13
+
14
+ @inject
15
+ def __init__(self,
16
+ auth_service: AuthService,
17
+ profile_service: ProfileService):
18
+ self.auth_service = auth_service
19
+ self.profile_service = profile_service
20
+
21
+ def post(self):
22
+ """
23
+ Handles POST requests to update the user's preferred language.
24
+ Expects a JSON body with a 'language' key, e.g., {"language": "en"}.
25
+ """
26
+ # 1. Authenticate the user from the current session.
27
+ auth_result = self.auth_service.verify()
28
+ if not auth_result.get("success"):
29
+ return jsonify(auth_result), auth_result.get("status_code")
30
+
31
+ user_identifier = auth_result.get('user_identifier')
32
+
33
+ # 2. Validate request body
34
+ data = request.get_json()
35
+ if not data or 'language' not in data:
36
+ return jsonify({"error_message": "Missing 'language' field in request body"}), 400
37
+
38
+ new_lang = data.get('language')
39
+
40
+ # 3. Call the service to perform the update
41
+ update_result = self.profile_service.update_user_language(user_identifier, new_lang)
42
+
43
+ if not update_result.get('success'):
44
+ return jsonify(update_result), 400
45
+
46
+ return jsonify({"message": "Language preference updated successfully"}), 200
@@ -6,26 +6,26 @@
6
6
  from flask import jsonify
7
7
  from flask.views import MethodView
8
8
  from iatoolkit.services.prompt_manager_service import PromptService
9
- from iatoolkit.common.auth import IAuthentication
9
+ from iatoolkit.services.auth_service import AuthService
10
10
  from injector import inject
11
11
  import logging
12
12
 
13
13
 
14
- class PromptView(MethodView):
14
+ class PromptApiView(MethodView):
15
15
  @inject
16
16
  def __init__(self,
17
- iauthentication: IAuthentication,
17
+ auth_service: AuthService,
18
18
  prompt_service: PromptService ):
19
- self.iauthentication = iauthentication
19
+ self.auth_service = auth_service
20
20
  self.prompt_service = prompt_service
21
21
 
22
22
  def get(self, company_short_name):
23
- # get access credentials
24
- iaut = self.iauthentication.verify(company_short_name)
25
- if not iaut.get("success"):
26
- return jsonify(iaut), 401
27
-
28
23
  try:
24
+ # get access credentials
25
+ auth_result = self.auth_service.verify(anonymous=True)
26
+ if not auth_result.get("success"):
27
+ return jsonify(auth_result), auth_result.get('status_code')
28
+
29
29
  response = self.prompt_service.get_user_prompts(company_short_name)
30
30
  if "error" in response:
31
31
  return {'error_message': response["error"]}, 402
@@ -33,5 +33,5 @@ class PromptView(MethodView):
33
33
  return response, 200
34
34
  except Exception as e:
35
35
  logging.exception(
36
- f"Error inesperado al obtener el historial de consultas para company {company_short_name}: {e}")
36
+ f"unexpected error getting company prompts: {e}")
37
37
  return jsonify({"error_message": str(e)}), 500
@@ -4,18 +4,24 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from flask.views import MethodView
7
- from flask import render_template
7
+ from flask import render_template, request, url_for, redirect, flash
8
8
  from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.branding_service import BrandingService
10
+ from iatoolkit.services.i18n_service import I18nService
9
11
  from injector import inject
10
12
  from itsdangerous import URLSafeTimedSerializer
11
- from flask import url_for, request
12
13
  import os
13
14
 
14
15
 
15
16
  class SignupView(MethodView):
16
17
  @inject
17
- def __init__(self, profile_service: ProfileService):
18
+ def __init__(self, profile_service: ProfileService,
19
+ branding_service: BrandingService,
20
+ i18n_service: I18nService):
18
21
  self.profile_service = profile_service
22
+ self.branding_service = branding_service # 3. Guardar la instancia
23
+ self.i18n_service = i18n_service
24
+
19
25
  self.serializer = URLSafeTimedSerializer(os.getenv("USER_VERIF_KEY"))
20
26
 
21
27
 
@@ -23,22 +29,23 @@ class SignupView(MethodView):
23
29
  # get company info
24
30
  company = self.profile_service.get_company_by_short_name(company_short_name)
25
31
  if not company:
26
- return render_template('error.html', message="Empresa no encontrada"), 404
32
+ return render_template('error.html',
33
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
27
34
 
28
- user_agent = request.user_agent
29
- is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
35
+ branding_data = self.branding_service.get_company_branding(company_short_name)
30
36
  return render_template('signup.html',
31
- company=company,
32
37
  company_short_name=company_short_name,
33
- is_mobile=is_mobile)
38
+ branding=branding_data)
34
39
 
35
40
  def post(self, company_short_name: str):
36
- # get company info
37
- company = self.profile_service.get_company_by_short_name(company_short_name)
38
- if not company:
39
- return render_template('error.html', message="Empresa no encontrada"), 404
40
-
41
41
  try:
42
+ company = self.profile_service.get_company_by_short_name(company_short_name)
43
+ if not company:
44
+ return render_template('error.html',
45
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
46
+
47
+ branding_data = self.branding_service.get_company_branding(company_short_name)
48
+
42
49
  first_name = request.form.get('first_name')
43
50
  last_name = request.form.get('last_name')
44
51
  email = request.form.get('email')
@@ -59,29 +66,27 @@ class SignupView(MethodView):
59
66
  verification_url=verification_url)
60
67
 
61
68
  if "error" in response:
69
+ flash(response["error"], 'error')
62
70
  return render_template(
63
- 'signup.html',
64
- company=company,
65
- company_short_name=company_short_name,
66
- form_data={
67
- "first_name": first_name,
68
- "last_name": last_name,
69
- "email": email,
70
- "password": password,
71
- "confirm_password": confirm_password
72
- },
73
- alert_message=response["error"]), 400
71
+ 'signup.html',
72
+ company_short_name=company_short_name,
73
+ branding=branding_data,
74
+ form_data={
75
+ "first_name": first_name,
76
+ "last_name": last_name,
77
+ "email": email,
78
+ "password": password,
79
+ "confirm_password": confirm_password
80
+ }), 400
74
81
 
75
- # all is OK
76
- return render_template(
77
- 'login.html',
78
- company=company,
79
- company_short_name=company_short_name,
80
- alert_icon='success',
81
- alert_message=response["message"]), 200
82
- except Exception as e:
83
- return render_template("error.html",
84
- company=company,
85
- company_short_name=company_short_name,
86
- message="Ha ocurrido un error inesperado."), 500
82
+ flash(response["message"], 'success')
83
+ return redirect(url_for('home', company_short_name=company_short_name))
87
84
 
85
+ except Exception as e:
86
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
87
+ return render_template(
88
+ "error.html",
89
+ company_short_name=company_short_name,
90
+ branding=branding_data,
91
+ message=message
92
+ ), 500
@@ -7,54 +7,28 @@ from flask.views import MethodView
7
7
  from flask import request, jsonify
8
8
  from iatoolkit.services.tasks_service import TaskService
9
9
  from iatoolkit.repositories.profile_repo import ProfileRepo
10
+ from iatoolkit.services.auth_service import AuthService
10
11
  from injector import inject
11
12
  from datetime import datetime
12
13
  import logging
13
14
  from typing import Optional
14
15
 
15
16
 
16
- class TaskView(MethodView):
17
+ class TaskApiView(MethodView):
17
18
  @inject
18
- def __init__(self, task_service: TaskService, profile_repo: ProfileRepo):
19
+ def __init__(self,
20
+ auth_service: AuthService,
21
+ task_service: TaskService,
22
+ profile_repo: ProfileRepo):
23
+ self.auth_service = auth_service
19
24
  self.task_service = task_service
20
25
  self.profile_repo = profile_repo
21
26
 
22
- def _authenticate_requesting_company_via_api_key(self) -> tuple[
23
- Optional[int], Optional[str], Optional[tuple[dict, int]]]:
24
- """
25
- Autentica a la compañía usando su API Key.
26
- Retorna (company_id, company_short_name, None) en éxito.
27
- Retorna (None, None, (error_json, status_code)) en fallo.
28
- """
29
- api_key_header = request.headers.get('Authorization')
30
- if not api_key_header or not api_key_header.startswith('Bearer '):
31
- return None, None, ({"error": "API Key faltante o mal formada en el header Authorization"}, 401)
32
-
33
- api_key_value = api_key_header.split('Bearer ')[1]
34
- try:
35
- api_key_entry = self.profile_repo.get_active_api_key_entry(api_key_value)
36
- if not api_key_entry:
37
- return None, None, ({"error": "API Key inválida o inactiva"}, 401)
38
-
39
- # api_key_entry.company ya está cargado por joinedload en get_active_api_key_entry
40
- if not api_key_entry.company: # Sanity check
41
- logging.error(
42
- f"ChatTokenRequest: API Key {api_key_value[:5]}... no tiene compañía asociada a pesar de ser válida.")
43
- return None, None, ({"error": "Error interno del servidor al verificar API Key"}, 500)
44
-
45
- return api_key_entry.company_id, api_key_entry.company.short_name, None
46
-
47
- except Exception as e:
48
- logging.exception(f"ChatTokenRequest: Error interno durante validación de API Key: {e}")
49
- return None, None, ({"error": "Error interno del servidor al validar API Key"}, 500)
50
-
51
-
52
27
  def post(self):
53
28
  try:
54
- # only requests with valid api-key are allowed
55
- auth_company_id, auth_company_short_name, error = self._authenticate_requesting_company_via_api_key()
56
- if error:
57
- return jsonify(error[0]), error[1]
29
+ auth_result = self.auth_service.verify(anonymous=True)
30
+ if not auth_result.get("success"):
31
+ return jsonify(auth_result), auth_result.get("status_code")
58
32
 
59
33
  req_data = request.get_json()
60
34
  files = request.files.getlist('files')
@@ -0,0 +1,55 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask.views import MethodView
7
+ from flask import request, jsonify
8
+ from iatoolkit.services.tasks_service import TaskService
9
+ from iatoolkit.repositories.profile_repo import ProfileRepo
10
+ from iatoolkit.services.auth_service import AuthService
11
+ from injector import inject
12
+ import logging
13
+ from typing import Optional
14
+
15
+
16
+ class TaskReviewApiView(MethodView):
17
+ @inject
18
+ def __init__(self,
19
+ auth_service: AuthService,
20
+ task_service: TaskService,
21
+ profile_repo: ProfileRepo):
22
+ self.auth_service = auth_service
23
+ self.task_service = task_service
24
+ self.profile_repo = profile_repo
25
+
26
+ def post(self, task_id: int):
27
+ auth_result = self.auth_service.verify(anonymous=True)
28
+ if not auth_result.get("success"):
29
+ return jsonify(auth_result), auth_result.get("status_code")
30
+
31
+ try:
32
+ req_data = request.get_json()
33
+ required_fields = ['review_user', 'approved']
34
+ for field in required_fields:
35
+ if field not in req_data:
36
+ return jsonify({"error": f"El campo {field} es requerido"}), 400
37
+
38
+ review_user = req_data.get('review_user', '')
39
+ approved = req_data.get('approved', False)
40
+ comment = req_data.get('comment', '')
41
+
42
+ new_task = self.task_service.review_task(
43
+ task_id=task_id,
44
+ review_user=review_user,
45
+ approved=approved,
46
+ comment=comment)
47
+
48
+ return jsonify({
49
+ "task_id": new_task.id,
50
+ "status": new_task.status.name
51
+ }), 200
52
+
53
+ except Exception as e:
54
+ logging.exception("Error al revisar la tarea: %s", str(e))
55
+ return jsonify({"error": str(e)}), 500
@@ -0,0 +1,60 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask import request, jsonify
7
+ from flask.views import MethodView
8
+ from iatoolkit.services.user_feedback_service import UserFeedbackService
9
+ from iatoolkit.services.auth_service import AuthService
10
+ from injector import inject
11
+ import logging
12
+
13
+
14
+ class UserFeedbackApiView(MethodView):
15
+ @inject
16
+ def __init__(self,
17
+ auth_service: AuthService,
18
+ user_feedback_service: UserFeedbackService ):
19
+ self.auth_service = auth_service
20
+ self.user_feedback_service = user_feedback_service
21
+
22
+ def post(self, company_short_name):
23
+ try:
24
+ # get access credentials
25
+ auth_result = self.auth_service.verify()
26
+ if not auth_result.get("success"):
27
+ return jsonify(auth_result), auth_result.get("status_code")
28
+
29
+ user_identifier = auth_result.get('user_identifier')
30
+
31
+ data = request.get_json()
32
+ if not data:
33
+ return jsonify({"error_message": "invalid json body"}), 402
34
+
35
+ # these validations are performed also in the frontend
36
+ # the are localized in the front
37
+ message = data.get("message")
38
+ if not message:
39
+ return jsonify({"error_message": "missing feedback message"}), 400
40
+
41
+ rating = data.get("rating")
42
+ if not rating:
43
+ return jsonify({"error_message": "missing rating"}), 400
44
+
45
+ response = self.user_feedback_service.new_feedback(
46
+ company_short_name=company_short_name,
47
+ message=message,
48
+ user_identifier=user_identifier,
49
+ rating=rating
50
+ )
51
+
52
+ if "error" in response:
53
+ return {'error_message': response["error"]}, 402
54
+
55
+ return response, 200
56
+ except Exception as e:
57
+ logging.exception(
58
+ f"unexpected error processing feedback for {company_short_name}: {e}")
59
+ return jsonify({"error_message": str(e)}), 500
60
+