iatoolkit 0.8.1__py3-none-any.whl → 0.63.4__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 (159) hide show
  1. iatoolkit/__init__.py +8 -34
  2. iatoolkit/base_company.py +14 -3
  3. iatoolkit/common/routes.py +83 -52
  4. iatoolkit/common/session_manager.py +0 -1
  5. iatoolkit/common/util.py +0 -27
  6. iatoolkit/iatoolkit.py +61 -46
  7. iatoolkit/infra/llm_client.py +7 -8
  8. iatoolkit/infra/openai_adapter.py +1 -1
  9. iatoolkit/infra/redis_session_manager.py +48 -2
  10. iatoolkit/repositories/database_manager.py +17 -2
  11. iatoolkit/repositories/models.py +31 -6
  12. iatoolkit/repositories/profile_repo.py +7 -2
  13. iatoolkit/services/auth_service.py +188 -0
  14. iatoolkit/services/branding_service.py +147 -0
  15. iatoolkit/services/dispatcher_service.py +10 -40
  16. iatoolkit/services/excel_service.py +15 -15
  17. iatoolkit/services/history_service.py +3 -12
  18. iatoolkit/services/jwt_service.py +15 -24
  19. iatoolkit/services/onboarding_service.py +43 -0
  20. iatoolkit/services/profile_service.py +97 -44
  21. iatoolkit/services/query_service.py +124 -81
  22. iatoolkit/services/tasks_service.py +1 -1
  23. iatoolkit/services/user_feedback_service.py +67 -31
  24. iatoolkit/services/user_session_context_service.py +112 -54
  25. iatoolkit/static/images/fernando.jpeg +0 -0
  26. iatoolkit/static/js/{chat_feedback.js → chat_feedback_button.js} +6 -11
  27. iatoolkit/static/js/chat_history_button.js +126 -0
  28. iatoolkit/static/js/chat_logout_button.js +36 -0
  29. iatoolkit/static/js/chat_main.js +130 -220
  30. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  31. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  32. iatoolkit/static/js/chat_reload_button.js +52 -0
  33. iatoolkit/static/styles/chat_iatoolkit.css +329 -507
  34. iatoolkit/static/styles/chat_modal.css +95 -56
  35. iatoolkit/static/styles/landing_page.css +182 -0
  36. iatoolkit/static/styles/onboarding.css +169 -0
  37. iatoolkit/system_prompts/query_main.prompt +3 -12
  38. iatoolkit/templates/_company_header.html +20 -0
  39. iatoolkit/templates/_login_widget.html +40 -0
  40. iatoolkit/templates/base.html +8 -3
  41. iatoolkit/templates/change_password.html +54 -37
  42. iatoolkit/templates/chat.html +149 -66
  43. iatoolkit/templates/chat_modals.html +47 -18
  44. iatoolkit/templates/error.html +41 -8
  45. iatoolkit/templates/forgot_password.html +37 -24
  46. iatoolkit/templates/index.html +140 -0
  47. iatoolkit/templates/login_simulation.html +34 -0
  48. iatoolkit/templates/onboarding_shell.html +105 -0
  49. iatoolkit/templates/signup.html +64 -66
  50. iatoolkit/views/base_login_view.py +81 -0
  51. iatoolkit/views/change_password_view.py +23 -12
  52. iatoolkit/views/external_login_view.py +61 -28
  53. iatoolkit/views/{file_store_view.py → file_store_api_view.py} +9 -2
  54. iatoolkit/views/forgot_password_view.py +23 -13
  55. iatoolkit/views/history_api_view.py +52 -0
  56. iatoolkit/views/home_view.py +58 -25
  57. iatoolkit/views/index_view.py +14 -0
  58. iatoolkit/views/init_context_api_view.py +68 -0
  59. iatoolkit/views/llmquery_api_view.py +45 -0
  60. iatoolkit/views/login_simulation_view.py +81 -0
  61. iatoolkit/views/login_view.py +118 -34
  62. iatoolkit/views/logout_api_view.py +45 -0
  63. iatoolkit/views/{prompt_view.py → prompt_api_view.py} +7 -7
  64. iatoolkit/views/signup_view.py +38 -29
  65. iatoolkit/views/{tasks_view.py → tasks_api_view.py} +10 -36
  66. iatoolkit/views/tasks_review_api_view.py +55 -0
  67. iatoolkit/views/{user_feedback_view.py → user_feedback_api_view.py} +16 -31
  68. iatoolkit/views/verify_user_view.py +13 -8
  69. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/METADATA +2 -2
  70. iatoolkit-0.63.4.dist-info/RECORD +113 -0
  71. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/top_level.txt +0 -1
  72. iatoolkit/common/auth.py +0 -200
  73. iatoolkit/static/images/arrow_up.png +0 -0
  74. iatoolkit/static/images/diagrama_iatoolkit.jpg +0 -0
  75. iatoolkit/static/images/logo_clinica.png +0 -0
  76. iatoolkit/static/images/logo_iatoolkit.png +0 -0
  77. iatoolkit/static/images/logo_maxxa.png +0 -0
  78. iatoolkit/static/images/logo_notaria.png +0 -0
  79. iatoolkit/static/images/logo_tarjeta.png +0 -0
  80. iatoolkit/static/images/logo_umayor.png +0 -0
  81. iatoolkit/static/images/upload.png +0 -0
  82. iatoolkit/static/js/chat_history.js +0 -117
  83. iatoolkit/templates/home.html +0 -201
  84. iatoolkit/templates/login.html +0 -43
  85. iatoolkit/views/chat_token_request_view.py +0 -98
  86. iatoolkit/views/chat_view.py +0 -51
  87. iatoolkit/views/download_file_view.py +0 -58
  88. iatoolkit/views/external_chat_login_view.py +0 -88
  89. iatoolkit/views/history_view.py +0 -57
  90. iatoolkit/views/llmquery_view.py +0 -65
  91. iatoolkit/views/tasks_review_view.py +0 -83
  92. iatoolkit-0.8.1.dist-info/RECORD +0 -175
  93. tests/__init__.py +0 -5
  94. tests/common/__init__.py +0 -0
  95. tests/common/test_auth.py +0 -279
  96. tests/common/test_routes.py +0 -42
  97. tests/common/test_session_manager.py +0 -59
  98. tests/common/test_util.py +0 -444
  99. tests/companies/__init__.py +0 -5
  100. tests/conftest.py +0 -36
  101. tests/infra/__init__.py +0 -5
  102. tests/infra/connectors/__init__.py +0 -5
  103. tests/infra/connectors/test_google_drive_connector.py +0 -107
  104. tests/infra/connectors/test_local_file_connector.py +0 -85
  105. tests/infra/connectors/test_s3_connector.py +0 -95
  106. tests/infra/test_call_service.py +0 -92
  107. tests/infra/test_database_manager.py +0 -59
  108. tests/infra/test_gemini_adapter.py +0 -137
  109. tests/infra/test_google_chat_app.py +0 -68
  110. tests/infra/test_llm_client.py +0 -165
  111. tests/infra/test_llm_proxy.py +0 -122
  112. tests/infra/test_mail_app.py +0 -94
  113. tests/infra/test_openai_adapter.py +0 -105
  114. tests/infra/test_redis_session_manager_service.py +0 -117
  115. tests/repositories/__init__.py +0 -5
  116. tests/repositories/test_database_manager.py +0 -87
  117. tests/repositories/test_document_repo.py +0 -76
  118. tests/repositories/test_llm_query_repo.py +0 -340
  119. tests/repositories/test_models.py +0 -38
  120. tests/repositories/test_profile_repo.py +0 -142
  121. tests/repositories/test_tasks_repo.py +0 -76
  122. tests/repositories/test_vs_repo.py +0 -107
  123. tests/services/__init__.py +0 -5
  124. tests/services/test_dispatcher_service.py +0 -274
  125. tests/services/test_document_service.py +0 -181
  126. tests/services/test_excel_service.py +0 -208
  127. tests/services/test_file_processor_service.py +0 -121
  128. tests/services/test_history_service.py +0 -164
  129. tests/services/test_jwt_service.py +0 -255
  130. tests/services/test_load_documents_service.py +0 -112
  131. tests/services/test_mail_service.py +0 -70
  132. tests/services/test_profile_service.py +0 -379
  133. tests/services/test_prompt_manager_service.py +0 -190
  134. tests/services/test_query_service.py +0 -243
  135. tests/services/test_search_service.py +0 -39
  136. tests/services/test_sql_service.py +0 -160
  137. tests/services/test_tasks_service.py +0 -252
  138. tests/services/test_user_feedback_service.py +0 -389
  139. tests/services/test_user_session_context_service.py +0 -132
  140. tests/views/__init__.py +0 -5
  141. tests/views/test_change_password_view.py +0 -191
  142. tests/views/test_chat_token_request_view.py +0 -188
  143. tests/views/test_chat_view.py +0 -98
  144. tests/views/test_download_file_view.py +0 -149
  145. tests/views/test_external_chat_login_view.py +0 -120
  146. tests/views/test_external_login_view.py +0 -102
  147. tests/views/test_file_store_view.py +0 -128
  148. tests/views/test_forgot_password_view.py +0 -142
  149. tests/views/test_history_view.py +0 -336
  150. tests/views/test_home_view.py +0 -61
  151. tests/views/test_llm_query_view.py +0 -154
  152. tests/views/test_login_view.py +0 -114
  153. tests/views/test_prompt_view.py +0 -111
  154. tests/views/test_signup_view.py +0 -140
  155. tests/views/test_tasks_review_view.py +0 -104
  156. tests/views/test_tasks_view.py +0 -130
  157. tests/views/test_user_feedback_view.py +0 -214
  158. tests/views/test_verify_user_view.py +0 -110
  159. {iatoolkit-0.8.1.dist-info → iatoolkit-0.63.4.dist-info}/WHEEL +0 -0
@@ -7,54 +7,138 @@ from flask.views import MethodView
7
7
  from flask import request, redirect, render_template, url_for
8
8
  from injector import inject
9
9
  from iatoolkit.services.profile_service import ProfileService
10
+ from iatoolkit.services.auth_service import AuthService
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.onboarding_service import OnboardingService
16
+ from iatoolkit.views.base_login_view import BaseLoginView
17
+ import logging
10
18
 
11
- class LoginView(MethodView):
12
- @inject
13
- def __init__(self, profile_service: ProfileService):
14
- self.profile_service = profile_service
15
19
 
16
- def get(self, company_short_name: str):
17
- # get company info
20
+ class LoginView(BaseLoginView):
21
+ """
22
+ Handles login for local users.
23
+ Authenticates and then delegates the path decision (fast/slow) to the base class.
24
+ """
25
+ def post(self, company_short_name: str):
18
26
  company = self.profile_service.get_company_by_short_name(company_short_name)
19
27
  if not company:
20
- return render_template('error.html', message="Empresa no encontrada"), 404
28
+ return render_template('error.html',
29
+ company_short_name=company_short_name,
30
+ message="Empresa no encontrada"), 404
21
31
 
22
- return render_template('login.html',
23
- company=company,
24
- company_short_name=company_short_name)
32
+ branding_data = self.branding_service.get_company_branding(company)
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
+
45
+ return render_template(
46
+ 'home.html',
47
+ company_short_name=company_short_name,
48
+ company=company,
49
+ branding=branding_data,
50
+ form_data={"email": email},
51
+ alert_message=auth_response["message"]
52
+ ), 400
53
+
54
+ user_identifier = auth_response['user_identifier']
55
+
56
+ # 3. define URL to call when slow path is finished
57
+ target_url = url_for('finalize_no_token',
58
+ company_short_name=company_short_name,
59
+ _external=True)
60
+
61
+ # 2. Delegate the path decision to the centralized logic.
62
+ try:
63
+ return self._handle_login_path(company, user_identifier, target_url)
64
+ except Exception as e:
65
+ return render_template("error.html",
66
+ company_short_name=company_short_name,
67
+ branding=branding_data,
68
+ message=f"Error processing login path: {str(e)}"), 500
69
+
70
+
71
+ class FinalizeContextView(MethodView):
72
+ """
73
+ Finalizes context loading in the slow path.
74
+ This view is invoked by the iframe inside onboarding_shell.html.
75
+ """
76
+ @inject
77
+ def __init__(self,
78
+ profile_service: ProfileService,
79
+ auth_service: AuthService,
80
+ query_service: QueryService,
81
+ prompt_service: PromptService,
82
+ branding_service: BrandingService,
83
+ onboarding_service: OnboardingService,
84
+ jwt_service: JWTService,
85
+ ):
86
+ self.profile_service = profile_service
87
+ self.jwt_service = jwt_service
88
+ self.query_service = query_service
89
+ self.prompt_service = prompt_service
90
+ self.branding_service = branding_service
91
+ self.onboarding_service = onboarding_service
92
+
93
+ def get(self, company_short_name: str, token: str = None):
94
+ session_info = self.profile_service.get_current_session_info()
95
+ if session_info:
96
+ # session exists, internal user
97
+ user_identifier = session_info.get('user_identifier')
98
+ token = ''
99
+ elif token:
100
+ # user identified by api-key
101
+ payload = self.jwt_service.validate_chat_jwt(token)
102
+ if not payload:
103
+ logging.warning("Fallo crítico: No se pudo leer el auth token.")
104
+ return redirect(url_for('index', company_short_name=company_short_name))
105
+
106
+ user_identifier = payload.get('user_identifier')
107
+ else:
108
+ logging.warning("Fallo crítico: missing session information or auth token")
109
+ return redirect(url_for('index', company_short_name=company_short_name))
25
110
 
26
- def post(self, company_short_name: str):
27
- # get company info
28
111
  company = self.profile_service.get_company_by_short_name(company_short_name)
29
112
  if not company:
30
- return render_template('error.html', message="Empresa no encontrada"), 404
113
+ return render_template('error.html',
114
+ company_short_name=company_short_name,
115
+ message="Empresa no encontrada"), 404
116
+ branding_data = self.branding_service.get_company_branding(company)
31
117
 
32
- email = request.form.get('email')
33
118
  try:
34
- password = request.form.get('password')
119
+ # 2. Finalize the context rebuild (the heavy task).
120
+ self.query_service.finalize_context_rebuild(
121
+ company_short_name=company_short_name,
122
+ user_identifier=user_identifier
123
+ )
124
+
125
+ # 3. render the chat page.
126
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
127
+ onboarding_cards = self.onboarding_service.get_onboarding_cards(company)
35
128
 
36
- response = self.profile_service.login(
129
+ return render_template(
130
+ "chat.html",
37
131
  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))
132
+ user_identifier=user_identifier,
133
+ branding=branding_data,
134
+ prompts=prompts,
135
+ onboarding_cards=onboarding_cards,
136
+ redeem_token=token
137
+ )
54
138
 
55
139
  except Exception as e:
56
140
  return render_template("error.html",
57
- company=company,
58
141
  company_short_name=company_short_name,
59
- message="Ha ocurrido un error inesperado."), 500
142
+ branding=branding_data,
143
+ message=f"An unexpected error occurred during context loading: {str(e)}"), 500
60
144
 
@@ -0,0 +1,45 @@
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
+
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
+ # 1. Get the authenticated user's
24
+ auth_result = self.auth_service.verify(anonymous=True)
25
+ if not auth_result.get("success"):
26
+ return jsonify(auth_result), auth_result.get("status_code", 401)
27
+
28
+ company = self.profile_service.get_company_by_short_name(company_short_name)
29
+ if not company:
30
+ return jsonify({"error": "Empresa no encontrada"}), 404
31
+
32
+ # get URL for redirection
33
+ url_for_redirect = company.parameters.get('external_urls', {}).get('logout_url')
34
+ if not url_for_redirect:
35
+ url_for_redirect = url_for('home', company_short_name=company_short_name)
36
+
37
+ # clear de session cookie
38
+ SessionManager.clear()
39
+
40
+ return {
41
+ 'status': 'success',
42
+ 'url': url_for_redirect,
43
+ }
44
+
45
+
@@ -6,24 +6,24 @@
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
23
  # get access credentials
24
- iaut = self.iauthentication.verify(company_short_name)
25
- if not iaut.get("success"):
26
- return jsonify(iaut), 401
24
+ auth_result = self.auth_service.verify(anonymous=True)
25
+ if not auth_result.get("success"):
26
+ return jsonify(auth_result), auth_result.get('status_code')
27
27
 
28
28
  try:
29
29
  response = self.prompt_service.get_user_prompts(company_short_name)
@@ -4,18 +4,20 @@
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, session, redirect
8
8
  from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.branding_service import BrandingService # 1. Importar BrandingService
9
10
  from injector import inject
10
11
  from itsdangerous import URLSafeTimedSerializer
11
- from flask import url_for, request
12
12
  import os
13
13
 
14
14
 
15
15
  class SignupView(MethodView):
16
16
  @inject
17
- def __init__(self, profile_service: ProfileService):
17
+ def __init__(self, profile_service: ProfileService,
18
+ branding_service: BrandingService):
18
19
  self.profile_service = profile_service
20
+ self.branding_service = branding_service # 3. Guardar la instancia
19
21
  self.serializer = URLSafeTimedSerializer(os.getenv("USER_VERIF_KEY"))
20
22
 
21
23
 
@@ -23,21 +25,27 @@ class SignupView(MethodView):
23
25
  # get company info
24
26
  company = self.profile_service.get_company_by_short_name(company_short_name)
25
27
  if not company:
26
- return render_template('error.html', message="Empresa no encontrada"), 404
28
+ return render_template('error.html',
29
+ company_short_name=company_short_name,
30
+ message="Empresa no encontrada"), 404
31
+
32
+ # Obtener los datos de branding
33
+ branding_data = self.branding_service.get_company_branding(company)
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()
30
35
  return render_template('signup.html',
31
36
  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
41
  # get company info
37
42
  company = self.profile_service.get_company_by_short_name(company_short_name)
38
43
  if not company:
39
- return render_template('error.html', message="Empresa no encontrada"), 404
44
+ return render_template('error.html',
45
+ company_short_name=company_short_name,
46
+ message="Empresa no encontrada"), 404
40
47
 
48
+ branding_data = self.branding_service.get_company_branding(company)
41
49
  try:
42
50
  first_name = request.form.get('first_name')
43
51
  last_name = request.form.get('last_name')
@@ -60,28 +68,29 @@ class SignupView(MethodView):
60
68
 
61
69
  if "error" in response:
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
74
-
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
71
+ 'signup.html',
72
+ company=company,
73
+ company_short_name=company_short_name,
74
+ branding=branding_data,
75
+ form_data={
76
+ "first_name": first_name,
77
+ "last_name": last_name,
78
+ "email": email,
79
+ "password": password,
80
+ "confirm_password": confirm_password
81
+ },
82
+ alert_message=response["error"]), 400
83
+
84
+ # Guardamos el mensaje de éxito en la sesión
85
+ session['alert_message'] = response["message"]
86
+ session['alert_icon'] = 'success'
87
+
88
+ # Redirigimos al usuario a la página de login
89
+ return redirect(url_for('home', company_short_name=company_short_name))
90
+
82
91
  except Exception as e:
83
92
  return render_template("error.html",
84
- company=company,
85
93
  company_short_name=company_short_name,
86
- message="Ha ocurrido un error inesperado."), 500
94
+ branding=branding_data,
95
+ message=f"Ha ocurrido un error inesperado: {str(e)}"), 500
87
96
 
@@ -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
@@ -3,59 +3,47 @@
3
3
  #
4
4
  # IAToolkit is open source software.
5
5
 
6
- from flask import request, jsonify, render_template
6
+ from flask import request, jsonify
7
7
  from flask.views import MethodView
8
8
  from iatoolkit.services.user_feedback_service import UserFeedbackService
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 UserFeedbackView(MethodView):
14
+ class UserFeedbackApiView(MethodView):
15
15
  @inject
16
16
  def __init__(self,
17
- iauthentication: IAuthentication,
17
+ auth_service: AuthService,
18
18
  user_feedback_service: UserFeedbackService ):
19
- self.iauthentication = iauthentication
19
+ self.auth_service = auth_service
20
20
  self.user_feedback_service = user_feedback_service
21
21
 
22
22
  def post(self, company_short_name):
23
+ # get access credentials
24
+ auth_result = self.auth_service.verify()
25
+ if not auth_result.get("success"):
26
+ return jsonify(auth_result), auth_result.get("status_code")
27
+
28
+ user_identifier = auth_result.get('user_identifier')
29
+
23
30
  data = request.get_json()
24
31
  if not data:
25
- return jsonify({"error_message": "Cuerpo de la solicitud JSON inválido o faltante"}), 400
26
-
27
- # get access credentials
28
- iaut = self.iauthentication.verify(company_short_name, data.get("external_user_id"))
29
- if not iaut.get("success"):
30
- return jsonify(iaut), 401
32
+ return jsonify({"error_message": "Cuerpo de la solicitud JSON inválido o faltante"}), 402
31
33
 
32
34
  message = data.get("message")
33
35
  if not message:
34
36
  return jsonify({"error_message": "Falta el mensaje de feedback"}), 400
35
37
 
36
- space = data.get("space")
37
- if not space:
38
- return jsonify({"error_message": "Falta el espacio de Google Chat"}), 400
39
-
40
- type = data.get("type")
41
- if not type:
42
- return jsonify({"error_message": "Falta el tipo de feedback"}), 400
43
-
44
38
  rating = data.get("rating")
45
39
  if not rating:
46
40
  return jsonify({"error_message": "Falta la calificación"}), 400
47
41
 
48
- external_user_id = data.get("external_user_id")
49
- local_user_id = data.get("local_user_id", 0)
50
-
51
42
  try:
52
43
  response = self.user_feedback_service.new_feedback(
53
44
  company_short_name=company_short_name,
54
45
  message=message,
55
- external_user_id=external_user_id,
56
- local_user_id=local_user_id,
57
- space=space,
58
- type=type,
46
+ user_identifier=user_identifier,
59
47
  rating=rating
60
48
  )
61
49
 
@@ -66,9 +54,6 @@ class UserFeedbackView(MethodView):
66
54
  except Exception as e:
67
55
  logging.exception(
68
56
  f"Error inesperado al procesar feedback para company {company_short_name}: {e}")
69
- if local_user_id:
70
- return render_template("error.html",
71
- message="Ha ocurrido un error inesperado."), 500
72
- else:
73
- return jsonify({"error_message": str(e)}), 500
57
+
58
+ return jsonify({"error_message": str(e)}), 500
74
59
 
@@ -4,17 +4,19 @@
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, url_for, redirect, session
8
8
  from iatoolkit.services.profile_service import ProfileService
9
9
  from itsdangerous import URLSafeTimedSerializer, SignatureExpired
10
+ from iatoolkit.services.branding_service import BrandingService # ¡Importante!
10
11
  from injector import inject
11
12
  import os
12
13
 
13
14
 
14
15
  class VerifyAccountView(MethodView):
15
16
  @inject
16
- def __init__(self, profile_service: ProfileService):
17
+ def __init__(self, profile_service: ProfileService, branding_service: BrandingService):
17
18
  self.profile_service = profile_service
19
+ self.branding_service = branding_service
18
20
  self.serializer = URLSafeTimedSerializer(os.getenv("USER_VERIF_KEY"))
19
21
 
20
22
  def get(self, company_short_name: str, token: str):
@@ -23,6 +25,7 @@ class VerifyAccountView(MethodView):
23
25
  if not company:
24
26
  return render_template('error.html', message="Empresa no encontrada"), 404
25
27
 
28
+ branding_data = self.branding_service.get_company_branding(company)
26
29
  try:
27
30
  # decode the token from the URL
28
31
  email = self.serializer.loads(token, salt='email-confirm', max_age=3600*5)
@@ -30,6 +33,7 @@ class VerifyAccountView(MethodView):
30
33
  return render_template('signup.html',
31
34
  company=company,
32
35
  company_short_name=company_short_name,
36
+ branding=branding_data,
33
37
  token=token,
34
38
  alert_message="El enlace de verificación ha expirado. Por favor, solicita uno nuevo."), 400
35
39
 
@@ -40,16 +44,17 @@ class VerifyAccountView(MethodView):
40
44
  'signup.html',
41
45
  company=company,
42
46
  company_short_name=company_short_name,
47
+ branding=branding_data,
43
48
  token=token,
44
49
  alert_message=response["error"]), 400
45
50
 
46
- return render_template('login.html',
47
- company=company,
48
- company_short_name=company_short_name,
49
- alert_icon='success',
50
- alert_message=response['message'])
51
+ # Guardamos el mensaje y el icono en la sesión manualmente
52
+ session['alert_message'] = response['message']
53
+ session['alert_icon'] = "success"
54
+ return redirect(url_for('home', company_short_name=company_short_name))
55
+
51
56
  except Exception as e:
52
57
  return render_template("error.html",
53
- company=company,
54
58
  company_short_name=company_short_name,
59
+ branding=branding_data,
55
60
  message="Ha ocurrido un error inesperado."), 500
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.8.1
3
+ Version: 0.63.4
4
4
  Summary: IAToolkit
5
5
  Author: Fernando Libedinsky
6
6
  License-Expression: MIT
7
- Requires-Python: >=3.11
7
+ Requires-Python: >=3.12
8
8
  Description-Content-Type: text/markdown
9
9
  Requires-Dist: aiohappyeyeballs==2.4.4
10
10
  Requires-Dist: aiohttp==3.11.9