iatoolkit 0.59.1__py3-none-any.whl → 0.67.0__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.

Potentially problematic release.


This version of iatoolkit might be problematic. Click here for more details.

Files changed (93) hide show
  1. iatoolkit/__init__.py +2 -0
  2. iatoolkit/base_company.py +2 -19
  3. iatoolkit/common/routes.py +28 -25
  4. iatoolkit/common/session_manager.py +2 -0
  5. iatoolkit/common/util.py +17 -6
  6. iatoolkit/company_registry.py +1 -2
  7. iatoolkit/iatoolkit.py +54 -20
  8. iatoolkit/locales/en.yaml +167 -0
  9. iatoolkit/locales/es.yaml +163 -0
  10. iatoolkit/repositories/database_manager.py +3 -3
  11. iatoolkit/repositories/document_repo.py +1 -1
  12. iatoolkit/repositories/models.py +3 -4
  13. iatoolkit/repositories/profile_repo.py +0 -4
  14. iatoolkit/services/auth_service.py +44 -32
  15. iatoolkit/services/branding_service.py +35 -27
  16. iatoolkit/services/configuration_service.py +140 -0
  17. iatoolkit/services/dispatcher_service.py +20 -18
  18. iatoolkit/services/document_service.py +5 -2
  19. iatoolkit/services/excel_service.py +15 -11
  20. iatoolkit/services/file_processor_service.py +4 -12
  21. iatoolkit/services/history_service.py +8 -7
  22. iatoolkit/services/i18n_service.py +104 -0
  23. iatoolkit/services/jwt_service.py +7 -9
  24. iatoolkit/services/language_service.py +79 -0
  25. iatoolkit/services/load_documents_service.py +4 -4
  26. iatoolkit/services/mail_service.py +9 -4
  27. iatoolkit/services/onboarding_service.py +10 -4
  28. iatoolkit/services/profile_service.py +59 -38
  29. iatoolkit/services/prompt_manager_service.py +20 -16
  30. iatoolkit/services/query_service.py +15 -14
  31. iatoolkit/services/sql_service.py +6 -2
  32. iatoolkit/services/user_feedback_service.py +70 -29
  33. iatoolkit/static/js/chat_feedback_button.js +80 -0
  34. iatoolkit/static/js/chat_help_content.js +124 -0
  35. iatoolkit/static/js/chat_history_button.js +110 -0
  36. iatoolkit/static/js/chat_logout_button.js +36 -0
  37. iatoolkit/static/js/chat_main.js +32 -184
  38. iatoolkit/static/js/{chat_onboarding.js → chat_onboarding_button.js} +0 -1
  39. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  40. iatoolkit/static/js/chat_reload_button.js +35 -0
  41. iatoolkit/static/styles/chat_iatoolkit.css +251 -205
  42. iatoolkit/static/styles/chat_modal.css +63 -95
  43. iatoolkit/static/styles/chat_public.css +107 -0
  44. iatoolkit/static/styles/landing_page.css +121 -167
  45. iatoolkit/templates/_company_header.html +20 -0
  46. iatoolkit/templates/_login_widget.html +10 -10
  47. iatoolkit/templates/base.html +36 -19
  48. iatoolkit/templates/change_password.html +24 -22
  49. iatoolkit/templates/chat.html +121 -93
  50. iatoolkit/templates/chat_modals.html +113 -74
  51. iatoolkit/templates/error.html +44 -8
  52. iatoolkit/templates/forgot_password.html +17 -15
  53. iatoolkit/templates/index.html +66 -81
  54. iatoolkit/templates/login_simulation.html +16 -5
  55. iatoolkit/templates/onboarding_shell.html +1 -2
  56. iatoolkit/templates/signup.html +22 -20
  57. iatoolkit/views/base_login_view.py +12 -1
  58. iatoolkit/views/change_password_view.py +50 -33
  59. iatoolkit/views/external_login_view.py +5 -11
  60. iatoolkit/views/file_store_api_view.py +7 -9
  61. iatoolkit/views/forgot_password_view.py +21 -19
  62. iatoolkit/views/help_content_api_view.py +54 -0
  63. iatoolkit/views/history_api_view.py +16 -12
  64. iatoolkit/views/home_view.py +61 -0
  65. iatoolkit/views/index_view.py +5 -34
  66. iatoolkit/views/init_context_api_view.py +16 -13
  67. iatoolkit/views/llmquery_api_view.py +38 -28
  68. iatoolkit/views/login_simulation_view.py +14 -2
  69. iatoolkit/views/login_view.py +48 -33
  70. iatoolkit/views/logout_api_view.py +49 -0
  71. iatoolkit/views/profile_api_view.py +46 -0
  72. iatoolkit/views/prompt_api_view.py +8 -8
  73. iatoolkit/views/signup_view.py +27 -25
  74. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  75. iatoolkit/views/tasks_review_api_view.py +55 -0
  76. iatoolkit/views/user_feedback_api_view.py +21 -32
  77. iatoolkit/views/verify_user_view.py +33 -26
  78. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/METADATA +40 -22
  79. iatoolkit-0.67.0.dist-info/RECORD +120 -0
  80. iatoolkit-0.67.0.dist-info/licenses/LICENSE +21 -0
  81. iatoolkit/static/js/chat_context_reload.js +0 -54
  82. iatoolkit/static/js/chat_feedback.js +0 -115
  83. iatoolkit/static/js/chat_history.js +0 -127
  84. iatoolkit/static/styles/chat_info.css +0 -53
  85. iatoolkit/templates/_branding_styles.html +0 -53
  86. iatoolkit/templates/_navbar.html +0 -9
  87. iatoolkit/templates/header.html +0 -31
  88. iatoolkit/templates/test.html +0 -9
  89. iatoolkit/views/chat_token_request_view.py +0 -98
  90. iatoolkit/views/tasks_review_view.py +0 -83
  91. iatoolkit-0.59.1.dist-info/RECORD +0 -111
  92. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/WHEEL +0 -0
  93. {iatoolkit-0.59.1.dist-info → iatoolkit-0.67.0.dist-info}/top_level.txt +0 -0
@@ -1,15 +1,25 @@
1
1
  {% extends "base.html" %}
2
2
 
3
3
  {% block title %}Prueba de Login para {{ company_short_name }}{% endblock %}
4
+ {% block styles %}
5
+ <style>
6
+ {{ branding.css_variables | safe }}
7
+ </style>
8
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_public.css') }}">
9
+ {% endblock %}
4
10
 
5
11
  {% block content %}
12
+ <div class="container mt-4">
13
+
14
+ {% include '_company_header.html' %}
15
+
6
16
  <div class="container-fluid">
7
17
  <div class="row flex-fill mt-5 justify-content-center">
8
18
  <div class="col-12 col-lg-6">
9
- <div class="border rounded p-4 p-md-5 shadow-sm bg-light">
10
- <h3 class="text-muted fw-semibold text-start mb-3">
11
- Login Externo para <span style="color:#0d6efd;">{{ company_short_name }}</span>
12
- </h3>
19
+ <div class="branded-form-container">
20
+ <h4 class="branded-form-title">
21
+ Login Externo para {{ company_short_name }}
22
+ </h4>
13
23
  <div class="text-center mb-4">
14
24
  <p class="text-muted widget-intro-text">
15
25
  Este formulario simula el inicio de una sesión externa. Al enviar, serás redirigido a la URL de login final.
@@ -22,7 +32,7 @@
22
32
  <label for="external_user_id" class="form-label d-block">External user ID</label>
23
33
  <input type="text" id="external_user_id" name="external_user_id" class="form-control" required>
24
34
  </div>
25
- <button type="submit" class="btn btn-primary">
35
+ <button type="submit" class="btn btn-branded-primary">
26
36
  Redirigir a External Login
27
37
  </button>
28
38
  </form>
@@ -30,5 +40,6 @@
30
40
  </div>
31
41
  </div>
32
42
  </div>
43
+ </div>
33
44
  {% endblock %}
34
45
 
@@ -11,7 +11,6 @@
11
11
  <link rel="stylesheet" href="{{ url_for('static', filename='styles/onboarding.css', _external=True) }}?v=6">
12
12
 
13
13
  <style>
14
- {# 1. Definimos las variables de la marca PRIMERO #}
15
14
  {% if branding and branding.css_variables %}
16
15
  {{ branding.css_variables|safe }}
17
16
  {% endif %}
@@ -71,7 +70,7 @@
71
70
  {% block scripts %}
72
71
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
73
72
 
74
- <script src="{{ url_for('static', filename='js/chat_onboarding.js', _external=True) }}"></script>
73
+ <script src="{{ url_for('static', filename='js/chat_onboarding_button.js', _external=True) }}"></script>
75
74
  <script>
76
75
  (function() {
77
76
  const cardsData = {{ onboarding_cards | tojson }};
@@ -1,28 +1,30 @@
1
1
  {% extends "base.html" %}
2
2
 
3
- {% block title %}Registro de Usuario - {{ company.name }}{% endblock %}
3
+ {% block title %}{{ t('ui.signup.title') }} - {{ branding.name }}{% endblock %}
4
4
 
5
- {% block content %}
6
- <!-- 1. Incluimos los estilos de branding reutilizables -->
7
- {% include '_branding_styles.html' %}
5
+ {% block styles %}
6
+ {# ¡Importante! Añadimos los estilos para el branding #}
7
+ <style>
8
+ {{ branding.css_variables | safe }}
9
+ </style>
10
+ <link rel="stylesheet" href="{{ url_for('static', filename='styles/chat_public.css') }}">
11
+ {% endblock %}
8
12
 
9
- <!-- Enlazamos la hoja de estilos de la landing page para reutilizar estilos -->
10
- <link rel="stylesheet" href="{{ iatoolkit_base_url }}/static/styles/landing_page.css">
13
+ {% block content %}
14
+ <div class="container mt-4">
11
15
 
12
- <!-- 2. Incluimos la barra de navegación reutilizable -->
13
- {% include '_navbar.html' %}
16
+ {% include '_company_header.html' %}
14
17
 
15
- <!-- 3. Sección contenedora para centrar el contenido -->
16
18
  <section class="hero-section">
17
19
  <div class="container">
18
20
  <div class="row justify-content-center">
19
21
  <!-- Se ha reducido el ancho de la columna a lg-6 y md-8 -->
20
22
  <div class="col-lg-6 col-md-8">
21
- <div class="border rounded p-4 p-md-5 shadow-sm bg-light">
22
- <h4 class="form-title fw-bold mb-3 text-center">Crea tu cuenta en {{ company.name }}</h4>
23
+ <div class="branded-form-container">
24
+ <h4 class="branded-form-title">{{ t('ui.signup.title') }}</h4>
23
25
  <form action="{{ url_for('signup', company_short_name=company_short_name) }}" method="post">
24
26
  <div class="mb-3">
25
- <label for="email" class="form-label text-secondary">Correo Electrónico</label>
27
+ <label for="email" class="form-label text-secondary">{{ t('ui.signup.email_label') }}</label>
26
28
  <input type="email" autocomplete="off" id="email" name="email"
27
29
  class="form-control" required
28
30
  value="{{ form_data.email if form_data else '' }}">
@@ -30,13 +32,13 @@
30
32
 
31
33
  <div class="row">
32
34
  <div class="col-md-6 mb-3">
33
- <label for="first_name" class="form-label text-secondary">Nombre</label>
35
+ <label for="first_name" class="form-label text-secondary">{{ t('ui.signup.first_name_label') }}</label>
34
36
  <input type="text" id="first_name" name="first_name"
35
37
  class="form-control" required
36
38
  value="{{ form_data.first_name if form_data else '' }}">
37
39
  </div>
38
40
  <div class="col-md-6 mb-3">
39
- <label for="last_name" class="form-label text-secondary">Apellido</label>
41
+ <label for="last_name" class="form-label text-secondary">{{ t('ui.signup.last_name_label') }}</label>
40
42
  <input type="text" id="last_name" name="last_name"
41
43
  class="form-control" required
42
44
  value="{{ form_data.last_name if form_data else '' }}">
@@ -44,31 +46,31 @@
44
46
  </div>
45
47
 
46
48
  <div class="mb-3">
47
- <label for="password" class="form-label text-secondary">Contraseña</label>
49
+ <label for="password" class="form-label text-secondary">{{ t('ui.signup.password_label') }}</label>
48
50
  <input type="password" id="password" name="password" class="form-control" required>
49
- <!-- Bloque de ayuda para la contraseña mejorado -->
50
51
  <div class="d-flex align-items-start text-muted mt-2" style="font-size: 0.8rem;">
51
52
  <i class="bi bi-info-circle me-2" style="font-size: 0.9rem; line-height: 1.4;"></i>
52
- <span>Debe contener al menos 8 caracteres, mayúscula, minúscula, número y un carácter especial.</span>
53
+ <span>{{ t('ui.change_password.password_instructions') }}</span>
53
54
  </div>
54
55
  </div>
55
56
 
56
57
  <div class="mb-3">
57
- <label for="confirm_password" class="form-label text-secondary">Confirmar Contraseña</label>
58
+ <label for="confirm_password" class="form-label text-secondary">{{ t('ui.signup.confirm_password_label') }}</label>
58
59
  <input type="password" id="confirm_password" name="confirm_password" class="form-control" required>
59
60
  </div>
60
61
 
61
62
  <!-- Botón actualizado con la clase de branding -->
62
- <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">Registrarse</button>
63
+ <button type="submit" class="btn btn-branded-primary w-100 fw-bold py-2 mt-3">{{ t('ui.signup.signup_button') }}</button>
63
64
  </form>
64
65
  <!-- Nota de privacidad -->
65
66
  <p class="text-muted small mb-0 text-center mt-4">
66
- 🔒 Valoramos tu privacidad. Tus datos se usarán exclusivamente para el funcionamiento de la plataforma.
67
+ {{ t('ui.signup.disclaimer') }}
67
68
  </p>
68
69
  </div>
69
70
  </div>
70
71
  </div>
71
72
  </div>
72
73
  </section>
74
+ </div>
73
75
 
74
76
  {% endblock %}
@@ -13,6 +13,8 @@ from iatoolkit.services.query_service import QueryService
13
13
  from iatoolkit.services.branding_service import BrandingService
14
14
  from iatoolkit.services.onboarding_service import OnboardingService
15
15
  from iatoolkit.services.prompt_manager_service import PromptService
16
+ from iatoolkit.services.i18n_service import I18nService
17
+ from iatoolkit.common.util import Utility
16
18
  from iatoolkit.services.jwt_service import JWTService
17
19
  from iatoolkit.repositories.models import Company
18
20
 
@@ -30,7 +32,9 @@ class BaseLoginView(MethodView):
30
32
  branding_service: BrandingService,
31
33
  prompt_service: PromptService,
32
34
  onboarding_service: OnboardingService,
33
- query_service: QueryService
35
+ query_service: QueryService,
36
+ i18n_service: I18nService,
37
+ utility: Utility
34
38
  ):
35
39
  self.profile_service = profile_service
36
40
  self.auth_service = auth_service
@@ -39,6 +43,8 @@ class BaseLoginView(MethodView):
39
43
  self.prompt_service = prompt_service
40
44
  self.onboarding_service = onboarding_service
41
45
  self.query_service = query_service
46
+ self.i18n_service = i18n_service
47
+ self.utility = utility
42
48
 
43
49
 
44
50
  def _handle_login_path(self,
@@ -70,6 +76,10 @@ class BaseLoginView(MethodView):
70
76
  else:
71
77
  # --- FAST PATH: Render the chat page directly ---
72
78
  prompts = self.prompt_service.get_user_prompts(company_short_name)
79
+
80
+ # Get the entire 'js_messages' block in the correct language.
81
+ js_translations = self.i18n_service.get_translation_block('js_messages')
82
+
73
83
  return render_template(
74
84
  "chat.html",
75
85
  company_short_name=company_short_name,
@@ -77,5 +87,6 @@ class BaseLoginView(MethodView):
77
87
  prompts=prompts,
78
88
  branding=branding_data,
79
89
  onboarding_cards=onboarding_cards,
90
+ js_translations=js_translations,
80
91
  redeem_token=redeem_token
81
92
  )
@@ -4,9 +4,10 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from flask.views import MethodView
7
- from flask import render_template, request, url_for, session, redirect
7
+ from flask import render_template, request, url_for, session, redirect, flash
8
8
  from iatoolkit.services.profile_service import ProfileService
9
9
  from iatoolkit.services.branding_service import BrandingService
10
+ from iatoolkit.services.i18n_service import I18nService
10
11
  from itsdangerous import URLSafeTimedSerializer, SignatureExpired
11
12
  from flask_bcrypt import Bcrypt
12
13
  from injector import inject
@@ -17,51 +18,65 @@ class ChangePasswordView(MethodView):
17
18
  @inject
18
19
  def __init__(self,
19
20
  profile_service: ProfileService,
20
- branding_service: BrandingService):
21
+ branding_service: BrandingService,
22
+ i18n_service: I18nService):
21
23
  self.profile_service = profile_service
22
- self.branding_service = branding_service # 3. Guardar la instancia
24
+ self.branding_service = branding_service
25
+ self.i18n_service = i18n_service
23
26
 
24
27
  self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
25
28
  self.bcrypt = Bcrypt()
26
29
 
27
30
  def get(self, company_short_name: str, token: str):
28
- # get company info
29
- company = self.profile_service.get_company_by_short_name(company_short_name)
30
- if not company:
31
- return render_template('error.html', message=f"Empresa no encontrada: {company_short_name}"), 404
31
+ try:
32
+ company = self.profile_service.get_company_by_short_name(company_short_name)
33
+ if not company:
34
+ return render_template('error.html',
35
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
32
36
 
33
- branding_data = self.branding_service.get_company_branding(company)
37
+ branding_data = self.branding_service.get_company_branding(company)
34
38
 
35
- try:
36
- # Decodificar el token
37
- email = self.serializer.loads(token, salt='password-reset', max_age=3600)
38
- except SignatureExpired as e:
39
- return render_template('forgot_password.html',
40
- branding=branding_data,
41
- alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo.")
39
+ try:
40
+ # Decodificar el token
41
+ email = self.serializer.loads(token, salt='password-reset', max_age=3600)
42
+ except SignatureExpired as e:
43
+ flash(self.i18n_service.t('errors.change_password.token_expired'), 'error')
44
+ return render_template('forgot_password.html',
45
+ branding=branding_data)
42
46
 
43
- return render_template('change_password.html',
44
- company_short_name=company_short_name,
45
- company=company,
46
- branding=branding_data,
47
- token=token, email=email)
47
+ return render_template('change_password.html',
48
+ company_short_name=company_short_name,
49
+ company=company,
50
+ branding=branding_data,
51
+ token=token,
52
+ email=email)
53
+ except Exception as e:
54
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
55
+ return render_template(
56
+ "error.html",
57
+ company_short_name=company_short_name,
58
+ branding=branding_data,
59
+ message=message
60
+ ), 500
48
61
 
49
62
  def post(self, company_short_name: str, token: str):
50
63
  # get company info
51
64
  company = self.profile_service.get_company_by_short_name(company_short_name)
52
65
  if not company:
53
- return render_template('error.html', message=f"Empresa no encontrada: {company_short_name}"), 404
66
+ return render_template('error.html',
67
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
54
68
 
55
69
  branding_data = self.branding_service.get_company_branding(company)
56
70
  try:
57
71
  # Decodificar el token
58
72
  email = self.serializer.loads(token, salt='password-reset', max_age=3600)
59
73
  except SignatureExpired:
74
+ flash(self.i18n_service.t('errors.change_password.token_expired'), 'error')
75
+
60
76
  return render_template('forgot_password.html',
61
77
  company_short_name=company_short_name,
62
78
  company=company,
63
- branding=branding_data,
64
- alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo.")
79
+ branding=branding_data)
65
80
 
66
81
  try:
67
82
  # Obtener datos del formulario
@@ -77,6 +92,8 @@ class ChangePasswordView(MethodView):
77
92
  )
78
93
 
79
94
  if "error" in response:
95
+ flash(response["error"], 'error')
96
+
80
97
  return render_template(
81
98
  'change_password.html',
82
99
  token=token,
@@ -85,16 +102,16 @@ class ChangePasswordView(MethodView):
85
102
  branding=branding_data,
86
103
  form_data={"temp_code": temp_code,
87
104
  "new_password": new_password,
88
- "confirm_password": confirm_password},
89
- alert_message=response["error"]), 400
105
+ "confirm_password": confirm_password}), 400
90
106
 
91
- # Éxito: Guardar mensaje en sesión y redirigir
92
- session['alert_message'] = "Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión."
93
- session['alert_icon'] = 'success'
94
- return redirect(url_for('index', company_short_name=company_short_name))
107
+ flash(self.i18n_service.t('flash_messages.password_changed_success'), 'success')
108
+ return redirect(url_for('home', company_short_name=company_short_name))
95
109
 
96
110
  except Exception as e:
97
- return render_template("error.html",
98
- company=company,
99
- company_short_name=company_short_name,
100
- message="Ha ocurrido un error inesperado."), 500
111
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
112
+ return render_template(
113
+ "error.html",
114
+ company_short_name=company_short_name,
115
+ branding=branding_data,
116
+ message=message
117
+ ), 500
@@ -15,22 +15,16 @@ class ExternalLoginView(BaseLoginView):
15
15
  Authenticates and then delegates the path decision (fast/slow) to the base class.
16
16
  """
17
17
  def post(self, company_short_name: str):
18
- data = request.get_json()
19
- if not data or 'user_identifier' not in data:
20
- return jsonify({"error": "Falta user_identifier"}), 400
18
+ # Authenticate the API call.
19
+ auth_result = self.auth_service.verify()
20
+ if not auth_result.get("success"):
21
+ return jsonify(auth_result), auth_result.get("status_code")
21
22
 
22
23
  company = self.profile_service.get_company_by_short_name(company_short_name)
23
24
  if not company:
24
25
  return jsonify({"error": "Empresa no encontrada"}), 404
25
26
 
26
- user_identifier = data.get('user_identifier')
27
- if not user_identifier:
28
- return jsonify({"error": "missing user_identifier"}), 404
29
-
30
- # 1. Authenticate the API call.
31
- auth_response = self.auth_service.verify()
32
- if not auth_response.get("success"):
33
- return jsonify(auth_response), 401
27
+ user_identifier = auth_result.get('user_identifier')
34
28
 
35
29
  # 2. Create the external user session.
36
30
  self.profile_service.create_external_user_profile_context(company, user_identifier)
@@ -15,24 +15,27 @@ import base64
15
15
  class FileStoreApiView(MethodView):
16
16
  @inject
17
17
  def __init__(self,
18
- iauthentication: AuthService,
18
+ auth_service: AuthService,
19
19
  doc_service: LoadDocumentsService,
20
20
  profile_repo: ProfileRepo,):
21
- self.iauthentication = iauthentication
21
+ self.auth_service = auth_service
22
22
  self.doc_service = doc_service
23
23
  self.profile_repo = profile_repo
24
24
 
25
25
  def post(self):
26
26
  try:
27
- req_data = request.get_json()
27
+ # 1. Authenticate the API request.
28
+ auth_result = self.auth_service.verify()
29
+ if not auth_result.get("success"):
30
+ return jsonify(auth_result), auth_result.get("status_code")
28
31
 
32
+ req_data = request.get_json()
29
33
  required_fields = ['company', 'filename', 'content']
30
34
  for field in required_fields:
31
35
  if field not in req_data:
32
36
  return jsonify({"error": f"El campo {field} es requerido"}), 400
33
37
 
34
38
  company_short_name = req_data.get('company', '')
35
- requested_name = req_data.get('username', 'external_user')
36
39
  filename = req_data.get('filename', False)
37
40
  base64_content = req_data.get('content', '')
38
41
  metadata = req_data.get('metadata', {})
@@ -42,11 +45,6 @@ class FileStoreApiView(MethodView):
42
45
  if not company:
43
46
  return jsonify({"error": f"La empresa {company_short_name} no existe"}), 400
44
47
 
45
- # get access credentials
46
- iaut = self.iauthentication.verify()
47
- if not iaut.get("success"):
48
- return jsonify(iaut), 401
49
-
50
48
  # get the file content from base64
51
49
  content = base64.b64decode(base64_content)
52
50
 
@@ -4,26 +4,31 @@
4
4
  # IAToolkit is open source software.
5
5
 
6
6
  from flask.views import MethodView
7
- from flask import render_template, request, url_for, redirect, session
7
+ from flask import render_template, request, url_for, redirect, session, flash
8
8
  from injector import inject
9
9
  from iatoolkit.services.profile_service import ProfileService
10
10
  from iatoolkit.services.branding_service import BrandingService
11
+ from iatoolkit.services.i18n_service import I18nService
11
12
  from itsdangerous import URLSafeTimedSerializer
12
13
  import os
13
14
 
14
15
  class ForgotPasswordView(MethodView):
15
16
  @inject
16
17
  def __init__(self, profile_service: ProfileService,
17
- branding_service: BrandingService):
18
+ branding_service: BrandingService,
19
+ i18n_service: I18nService):
18
20
  self.profile_service = profile_service
19
- self.branding_service = branding_service # 3. Guardar la instancia
21
+ self.branding_service = branding_service
22
+ self.i18n_service = i18n_service
23
+
20
24
  self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
21
25
 
22
26
  def get(self, company_short_name: str):
23
27
  # get company info
24
28
  company = self.profile_service.get_company_by_short_name(company_short_name)
25
29
  if not company:
26
- return render_template('error.html', message="Empresa no encontrada"), 404
30
+ return render_template('error.html',
31
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
27
32
 
28
33
  branding_data = self.branding_service.get_company_branding(company)
29
34
  return render_template('forgot_password.html',
@@ -33,11 +38,14 @@ class ForgotPasswordView(MethodView):
33
38
  )
34
39
 
35
40
  def post(self, company_short_name: str):
36
- company = self.profile_service.get_company_by_short_name(company_short_name)
37
- if not company:
38
- return render_template('error.html', message="Empresa no encontrada"), 404
39
41
 
40
42
  try:
43
+ company = self.profile_service.get_company_by_short_name(company_short_name)
44
+ if not company:
45
+ return render_template('error.html',
46
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
47
+
48
+ branding_data = self.branding_service.get_company_branding(company)
41
49
  email = request.form.get('email')
42
50
 
43
51
  # create a safe token and url for it
@@ -48,23 +56,17 @@ class ForgotPasswordView(MethodView):
48
56
 
49
57
  response = self.profile_service.forgot_password(email=email, reset_url=reset_url)
50
58
  if "error" in response:
51
- branding_data = self.branding_service.get_company_branding(company)
59
+ flash(response["error"], 'error')
52
60
  return render_template(
53
61
  'forgot_password.html',
54
62
  company=company,
55
63
  company_short_name=company_short_name,
56
64
  branding=branding_data,
57
- form_data={"email": email},
58
- alert_message=response["error"]), 400
65
+ form_data={"email": email}), 400
59
66
 
60
- # Guardamos el mensaje y el icono en la sesión manualmente
61
- session['alert_message'] = "Si tu correo está registrado, recibirás un enlace para restablecer tu contraseña."
62
- session['alert_icon'] = "success"
63
- return redirect(url_for('index', company_short_name=company_short_name))
67
+ flash(self.i18n_service.t('flash_messages.forgot_password_success'), 'success')
68
+ return redirect(url_for('home', company_short_name=company_short_name))
64
69
 
65
70
  except Exception as e:
66
- return render_template("error.html",
67
- company=company,
68
- company_short_name=company_short_name,
69
- message="Ha ocurrido un error inesperado."), 500
70
-
71
+ flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
72
+ return redirect(url_for('home', company_short_name=company_short_name))
@@ -0,0 +1,54 @@
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.configuration_service import ConfigurationService
9
+ from iatoolkit.services.i18n_service import I18nService
10
+ from iatoolkit.services.auth_service import AuthService
11
+ from injector import inject
12
+ import logging
13
+
14
+
15
+ class HelpContentApiView(MethodView):
16
+ """
17
+ Handles requests from the web UI to fetch a user's query history.
18
+ Authentication is based on the active Flask session.
19
+ """
20
+
21
+ @inject
22
+ def __init__(self,
23
+ auth_service: AuthService,
24
+ config_service: ConfigurationService,
25
+ i18n_service: I18nService):
26
+ self.auth_service = auth_service
27
+ self.config_service = config_service
28
+ self.i18n_service = i18n_service
29
+
30
+ def post(self, company_short_name: str):
31
+ try:
32
+ # 1. Get the authenticated user's
33
+ auth_result = self.auth_service.verify()
34
+ if not auth_result.get("success"):
35
+ return jsonify(auth_result), auth_result.get("status_code")
36
+
37
+ user_identifier = auth_result.get('user_identifier')
38
+
39
+ # 2. Call the config service with the unified identifier.
40
+ response = self.config_service.get_company_content(
41
+ company_short_name=company_short_name,
42
+ content_key='help_content' # specific key for this service
43
+ )
44
+
45
+ if "error" in response:
46
+ # Handle errors reported by the service itself.
47
+ return jsonify({'error_message': response["error"]}), 400
48
+
49
+ return jsonify(response), 200
50
+
51
+ except Exception as e:
52
+ logging.exception(
53
+ f"Unexpected error fetching help_content for {company_short_name}: {e}")
54
+ return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500
@@ -6,7 +6,8 @@
6
6
  from flask import request, jsonify
7
7
  from flask.views import MethodView
8
8
  from iatoolkit.services.history_service import HistoryService
9
- from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.auth_service import AuthService
10
+ from iatoolkit.services.i18n_service import I18nService
10
11
  from injector import inject
11
12
  import logging
12
13
 
@@ -19,20 +20,23 @@ class HistoryApiView(MethodView):
19
20
 
20
21
  @inject
21
22
  def __init__(self,
22
- profile_service: ProfileService,
23
- history_service: HistoryService):
24
- self.profile_service = profile_service
23
+ auth_service: AuthService,
24
+ history_service: HistoryService,
25
+ i18n_service: I18nService):
26
+ self.auth_service = auth_service
25
27
  self.history_service = history_service
28
+ self.i18n_service = i18n_service
29
+
26
30
 
27
31
  def post(self, company_short_name: str):
28
- # 1. Get the authenticated user's info from the unified session.
29
- session_info = self.profile_service.get_current_session_info()
30
- user_identifier = session_info.get("user_identifier")
32
+ try:
33
+ # 1. Get the authenticated user's
34
+ auth_result = self.auth_service.verify()
35
+ if not auth_result.get("success"):
36
+ return jsonify(auth_result), auth_result.get("status_code")
31
37
 
32
- if not user_identifier:
33
- return jsonify({'error_message': 'Usuario no autenticado o sesión inválida'}), 401
38
+ user_identifier = auth_result.get('user_identifier')
34
39
 
35
- try:
36
40
  # 2. Call the history service with the unified identifier.
37
41
  # The service's signature should now only expect user_identifier.
38
42
  response = self.history_service.get_history(
@@ -48,5 +52,5 @@ class HistoryApiView(MethodView):
48
52
 
49
53
  except Exception as e:
50
54
  logging.exception(
51
- f"Unexpected error fetching history for {company_short_name}/{user_identifier}: {e}")
52
- return jsonify({"error_message": "Ha ocurrido un error inesperado en el servidor."}), 500
55
+ f"Unexpected error: {e}")
56
+ return jsonify({"error_message": self.i18n_service.t('errors.general.unexpected_error', error=str(e))}), 500