iatoolkit 0.7.4__py3-none-any.whl → 0.7.6__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 (57) hide show
  1. common/__init__.py +0 -0
  2. common/auth.py +200 -0
  3. common/exceptions.py +46 -0
  4. common/routes.py +86 -0
  5. common/session_manager.py +25 -0
  6. common/util.py +358 -0
  7. iatoolkit/iatoolkit.py +3 -3
  8. {iatoolkit-0.7.4.dist-info → iatoolkit-0.7.6.dist-info}/METADATA +1 -1
  9. iatoolkit-0.7.6.dist-info/RECORD +80 -0
  10. iatoolkit-0.7.6.dist-info/top_level.txt +6 -0
  11. infra/__init__.py +5 -0
  12. infra/call_service.py +140 -0
  13. infra/connectors/__init__.py +5 -0
  14. infra/connectors/file_connector.py +17 -0
  15. infra/connectors/file_connector_factory.py +57 -0
  16. infra/connectors/google_cloud_storage_connector.py +53 -0
  17. infra/connectors/google_drive_connector.py +68 -0
  18. infra/connectors/local_file_connector.py +46 -0
  19. infra/connectors/s3_connector.py +33 -0
  20. infra/gemini_adapter.py +356 -0
  21. infra/google_chat_app.py +57 -0
  22. infra/llm_client.py +430 -0
  23. infra/llm_proxy.py +139 -0
  24. infra/llm_response.py +40 -0
  25. infra/mail_app.py +145 -0
  26. infra/openai_adapter.py +90 -0
  27. infra/redis_session_manager.py +76 -0
  28. repositories/__init__.py +5 -0
  29. repositories/database_manager.py +95 -0
  30. repositories/document_repo.py +33 -0
  31. repositories/llm_query_repo.py +91 -0
  32. repositories/models.py +309 -0
  33. repositories/profile_repo.py +118 -0
  34. repositories/tasks_repo.py +52 -0
  35. repositories/vs_repo.py +139 -0
  36. views/__init__.py +5 -0
  37. views/change_password_view.py +91 -0
  38. views/chat_token_request_view.py +98 -0
  39. views/chat_view.py +51 -0
  40. views/download_file_view.py +58 -0
  41. views/external_chat_login_view.py +88 -0
  42. views/external_login_view.py +40 -0
  43. views/file_store_view.py +58 -0
  44. views/forgot_password_view.py +64 -0
  45. views/history_view.py +57 -0
  46. views/home_view.py +34 -0
  47. views/llmquery_view.py +65 -0
  48. views/login_view.py +60 -0
  49. views/prompt_view.py +37 -0
  50. views/signup_view.py +87 -0
  51. views/tasks_review_view.py +83 -0
  52. views/tasks_view.py +98 -0
  53. views/user_feedback_view.py +74 -0
  54. views/verify_user_view.py +55 -0
  55. iatoolkit-0.7.4.dist-info/RECORD +0 -30
  56. iatoolkit-0.7.4.dist-info/top_level.txt +0 -2
  57. {iatoolkit-0.7.4.dist-info → iatoolkit-0.7.6.dist-info}/WHEEL +0 -0
@@ -0,0 +1,91 @@
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 render_template, request
8
+ from services.profile_service import ProfileService
9
+ from itsdangerous import URLSafeTimedSerializer, SignatureExpired
10
+ from flask_bcrypt import Bcrypt
11
+ from injector import inject
12
+ import os
13
+
14
+
15
+ class ChangePasswordView(MethodView):
16
+ @inject
17
+ def __init__(self, profile_service: ProfileService):
18
+ self.profile_service = profile_service
19
+
20
+ self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
21
+ self.bcrypt = Bcrypt()
22
+
23
+ def get(self, company_short_name: str, token: str):
24
+ # get company info
25
+ company = self.profile_service.get_company_by_short_name(company_short_name)
26
+ if not company:
27
+ return render_template('error.html', message=f"Empresa no encontrada: {company_short_name}"), 404
28
+
29
+ try:
30
+ # Decodificar el token
31
+ email = self.serializer.loads(token, salt='password-reset', max_age=3600)
32
+ except SignatureExpired as e:
33
+ return render_template('forgot_password.html',
34
+ alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo.")
35
+
36
+ return render_template('change_password.html',
37
+ company_short_name=company_short_name,
38
+ company=company,
39
+ token=token, email=email)
40
+
41
+ def post(self, company_short_name: str, token: str):
42
+ # get company info
43
+ company = self.profile_service.get_company_by_short_name(company_short_name)
44
+ if not company:
45
+ return render_template('error.html', message=f"Empresa no encontrada: {company_short_name}"), 404
46
+
47
+ try:
48
+ # Decodificar el token
49
+ email = self.serializer.loads(token, salt='password-reset', max_age=3600)
50
+ except SignatureExpired:
51
+ return render_template('forgot_password.html',
52
+ company_short_name=company_short_name,
53
+ company=company,
54
+ alert_message="El enlace de cambio de contraseña ha expirado. Por favor, solicita uno nuevo.")
55
+
56
+ try:
57
+ # Obtener datos del formulario
58
+ temp_code = request.form.get('temp_code')
59
+ new_password = request.form.get('new_password')
60
+ confirm_password = request.form.get('confirm_password')
61
+
62
+ response = self.profile_service.change_password(
63
+ email=email,
64
+ temp_code=temp_code,
65
+ new_password=new_password,
66
+ confirm_password=confirm_password
67
+ )
68
+
69
+ if "error" in response:
70
+ return render_template(
71
+ 'change_password.html',
72
+ token=token,
73
+ company_short_name=company_short_name,
74
+ company=company,
75
+ form_data={"temp_code": temp_code,
76
+ "new_password": new_password,
77
+ "confirm_password": confirm_password},
78
+ alert_message=response["error"]), 400
79
+
80
+
81
+ return render_template('login.html',
82
+ company_short_name=company_short_name,
83
+ company=company,
84
+ alert_icon='success',
85
+ alert_message="Tu contraseña ha sido restablecida exitosamente. Ahora puedes iniciar sesión.")
86
+
87
+ except Exception as e:
88
+ return render_template("error.html",
89
+ company=company,
90
+ company_short_name=company_short_name,
91
+ message="Ha ocurrido un error inesperado."), 500
@@ -0,0 +1,98 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask import request, jsonify, current_app
7
+ from flask.views import MethodView
8
+ from injector import inject
9
+ import logging
10
+ from repositories.profile_repo import ProfileRepo
11
+ from services.jwt_service import JWTService
12
+ from typing import Optional
13
+
14
+
15
+ # Necesitaremos JWT_EXPIRATION_SECONDS_CHAT de la configuración de la app
16
+ # Se podría inyectar o acceder globalmente.
17
+
18
+ class ChatTokenRequestView(MethodView):
19
+ @inject
20
+ def __init__(self, profile_repo: ProfileRepo, jwt_service: JWTService):
21
+ self.profile_repo = profile_repo
22
+ self.jwt_service = jwt_service
23
+
24
+ def _authenticate_requesting_company_via_api_key(self) -> tuple[
25
+ Optional[int], Optional[str], Optional[tuple[dict, int]]]:
26
+ """
27
+ Autentica a la compañía que solicita el token JWT usando su API Key.
28
+ Retorna (company_id, company_short_name, None) en éxito.
29
+ Retorna (None, None, (error_json, status_code)) en fallo.
30
+ """
31
+ api_key_header = request.headers.get('Authorization')
32
+ if not api_key_header or not api_key_header.startswith('Bearer '):
33
+ return None, None, ({"error": "API Key faltante o mal formada en el header Authorization"}, 401)
34
+
35
+ api_key_value = api_key_header.split('Bearer ')[1]
36
+ try:
37
+ api_key_entry = self.profile_repo.get_active_api_key_entry(api_key_value)
38
+ if not api_key_entry:
39
+ return None, None, ({"error": "API Key inválida o inactiva"}, 401)
40
+
41
+ # api_key_entry.company ya está cargado por joinedload en get_active_api_key_entry
42
+ if not api_key_entry.company: # Sanity check
43
+ logging.error(
44
+ f"ChatTokenRequest: API Key {api_key_value[:5]}... no tiene compañía asociada a pesar de ser válida.")
45
+ return None, None, ({"error": "Error interno del servidor al verificar API Key"}, 500)
46
+
47
+ return api_key_entry.company_id, api_key_entry.company.short_name, None
48
+
49
+ except Exception as e:
50
+ logging.exception(f"ChatTokenRequest: Error interno durante validación de API Key: {e}")
51
+ return None, None, ({"error": "Error interno del servidor al validar API Key"}, 500)
52
+
53
+ def post(self):
54
+ """
55
+ Genera un JWT para una sesión de chat.
56
+ Autenticado por API Key de la empresa.
57
+ Requiere JSON body:
58
+ {"company_short_name": "target_company_name",
59
+ "external_user_id": "user_abc"
60
+ }
61
+ """
62
+ # only requests with valid api-key are allowed
63
+ auth_company_id, auth_company_short_name, error = self._authenticate_requesting_company_via_api_key()
64
+ if error:
65
+ return jsonify(error[0]), error[1]
66
+
67
+ # get the json fields from the request body
68
+ data = request.get_json()
69
+ if not data:
70
+ return jsonify({"error": "Cuerpo de la solicitud JSON faltante"}), 400
71
+
72
+ target_company_short_name = data.get('company_short_name')
73
+ external_user_id = data.get('external_user_id')
74
+
75
+ if not target_company_short_name or not external_user_id:
76
+ return jsonify(
77
+ {"error": "Faltan 'company_short_name' o 'external_user_id' en el cuerpo de la solicitud"}), 401
78
+
79
+ # Verificar que la API Key usada pertenezca a la empresa para la cual se solicita el token
80
+ if auth_company_short_name != target_company_short_name:
81
+ return jsonify({
82
+ "error": f"API Key no autorizada para generar tokens para la compañía '{target_company_short_name}'"}), 403
83
+
84
+ jwt_expiration_seconds = current_app.config.get('JWT_EXPIRATION_SECONDS_CHAT', 3600)
85
+
86
+ # Aquí, auth_company_id es el ID de la compañía para la que se generará el token.
87
+ # auth_company_short_name es su nombre corto.
88
+ token = self.jwt_service.generate_chat_jwt(
89
+ company_id=auth_company_id,
90
+ company_short_name=auth_company_short_name, # Usamos el short_name autenticado
91
+ external_user_id=external_user_id,
92
+ expires_delta_seconds=jwt_expiration_seconds
93
+ )
94
+
95
+ if token:
96
+ return jsonify({"chat_jwt": token}), 200
97
+ else:
98
+ return jsonify({"error": "No se pudo generar el token de chat"}), 500
views/chat_view.py ADDED
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask import render_template, request, jsonify
7
+ from services.profile_service import ProfileService
8
+ from flask.views import MethodView
9
+ from injector import inject
10
+ import os
11
+ from common.auth import IAuthentication
12
+ from services.prompt_manager_service import PromptService
13
+
14
+
15
+ class ChatView(MethodView):
16
+ @inject
17
+ def __init__(self,
18
+ iauthentication: IAuthentication,
19
+ prompt_service: PromptService,
20
+ profile_service: ProfileService):
21
+ self.iauthentication = iauthentication
22
+ self.profile_service = profile_service
23
+ self.prompt_service = prompt_service
24
+
25
+ def get(self, company_short_name: str):
26
+ # get access credentials
27
+ iaut = self.iauthentication.verify(company_short_name)
28
+ if not iaut.get("success"):
29
+ return jsonify(iaut), 401
30
+
31
+ user_agent = request.user_agent
32
+ is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
33
+ alert_message = request.args.get('alert_message', None)
34
+
35
+ # 1. get company info
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
+
40
+ # 2. get the company prompts
41
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
42
+
43
+ return render_template("chat.html",
44
+ company=company,
45
+ company_short_name=company_short_name,
46
+ is_mobile=is_mobile,
47
+ alert_message=alert_message,
48
+ alert_icon='success' if alert_message else None,
49
+ iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL', 'http://localhost:5000'),
50
+ prompts=prompts
51
+ )
@@ -0,0 +1,58 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ import logging
7
+ import os
8
+
9
+ from flask import current_app, jsonify, send_from_directory
10
+ from flask.views import MethodView
11
+ from injector import inject
12
+
13
+ from common.auth import IAuthentication
14
+ from services.excel_service import ExcelService
15
+ from services.profile_service import ProfileService
16
+
17
+
18
+ class DownloadFileView(MethodView):
19
+ @inject
20
+ def __init__(self, iauthentication: IAuthentication, profile_service: ProfileService, excel_service: ExcelService):
21
+ self.iauthentication = iauthentication
22
+ self.profile_service = profile_service
23
+ self.excel_service = excel_service
24
+
25
+ def get(self, company_short_name: str, external_user_id: str, filename: str):
26
+ if not external_user_id:
27
+ return jsonify({"error": "Falta external_user_id"}), 400
28
+
29
+ iauth = self.iauthentication.verify(
30
+ company_short_name,
31
+ body_external_user_id=external_user_id
32
+ )
33
+ if not iauth.get("success"):
34
+ return jsonify(iauth), 401
35
+
36
+ company = self.profile_service.get_company_by_short_name(company_short_name)
37
+ if not company:
38
+ return jsonify({"error": "Empresa no encontrada"}), 404
39
+
40
+ file_validation = self.excel_service.validate_file_access(filename)
41
+ if file_validation:
42
+ return file_validation
43
+
44
+ temp_dir = os.path.join(current_app.root_path, 'static', 'temp')
45
+
46
+ try:
47
+ response = send_from_directory(
48
+ temp_dir,
49
+ filename,
50
+ as_attachment=True,
51
+ mimetype='application/octet-stream'
52
+ )
53
+ logging.info(f"Archivo descargado via API: {filename}")
54
+ return response
55
+ except Exception as e:
56
+ logging.error(f"Error descargando archivo {filename}: {str(e)}")
57
+ return jsonify({"error": "Error descargando archivo"}), 500
58
+
@@ -0,0 +1,88 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ import os
7
+ import logging
8
+ from flask import request, jsonify, render_template
9
+ from flask.views import MethodView
10
+ from injector import inject
11
+ from common.auth import IAuthentication
12
+ from services.profile_service import ProfileService
13
+ from services.query_service import QueryService
14
+ from services.prompt_manager_service import PromptService
15
+ from services.jwt_service import JWTService
16
+
17
+ class ExternalChatLoginView(MethodView):
18
+ @inject
19
+ def __init__(self,
20
+ profile_service: ProfileService,
21
+ query_service: QueryService,
22
+ prompt_service: PromptService,
23
+ iauthentication: IAuthentication,
24
+ jwt_service: JWTService
25
+ ):
26
+ self.profile_service = profile_service
27
+ self.query_service = query_service
28
+ self.prompt_service = prompt_service
29
+ self.iauthentication = iauthentication
30
+ self.jwt_service = jwt_service
31
+
32
+ def post(self, company_short_name: str):
33
+ data = request.get_json()
34
+ if not data or 'external_user_id' not in data:
35
+ return jsonify({"error": "Falta external_user_id"}), 400
36
+
37
+ external_user_id = data['external_user_id']
38
+ # 1. get access credentials
39
+ iaut = self.iauthentication.verify(
40
+ company_short_name,
41
+ body_external_user_id=external_user_id
42
+ )
43
+ if not iaut.get("success"):
44
+ return jsonify(iaut), 401
45
+
46
+ company = self.profile_service.get_company_by_short_name(company_short_name)
47
+ if not company:
48
+ return jsonify({"error": "Empresa no encontrada"}), 404
49
+
50
+ try:
51
+ # 1. generate a new JWT, our secure access token.
52
+ token = self.jwt_service.generate_chat_jwt(
53
+ company_id=company.id,
54
+ company_short_name=company.short_name,
55
+ external_user_id=external_user_id,
56
+ expires_delta_seconds=3600 * 8 # 8 horas
57
+ )
58
+ if not token:
59
+ raise Exception("No se pudo generar el token de sesión (JWT).")
60
+
61
+ # 2. init the company/user LLM context.
62
+ self.query_service.llm_init_context(
63
+ company_short_name=company_short_name,
64
+ external_user_id=external_user_id
65
+ )
66
+
67
+ # 3. get the prompt list from backend
68
+ prompts = self.prompt_service.get_user_prompts(company_short_name)
69
+
70
+ # 4. render the chat page with the company/user information.
71
+ user_agent = request.user_agent
72
+ is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
73
+
74
+ chat_html = render_template("chat.html",
75
+ company=company,
76
+ company_short_name=company_short_name,
77
+ external_user_id=external_user_id,
78
+ is_mobile=is_mobile,
79
+ auth_method='jwt', # login method is JWT
80
+ session_jwt=token, # pass the token to the front-end
81
+ iatoolkit_base_url=os.getenv('IATOOLKIT_BASE_URL'),
82
+ prompts=prompts,
83
+ external_login=True)
84
+ return chat_html, 200
85
+
86
+ except Exception as e:
87
+ logging.exception(f"Error al inicializar el chat para {company_short_name}/{external_user_id}: {e}")
88
+ return jsonify({"error": "Error interno al iniciar el chat"}), 500
@@ -0,0 +1,40 @@
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 injector import inject
8
+ from common.auth import IAuthentication
9
+ from services.query_service import QueryService
10
+ from flask import jsonify
11
+ import logging
12
+
13
+ class ExternalLoginView(MethodView):
14
+
15
+ @inject
16
+ def __init__(self,
17
+ iauthentication: IAuthentication,
18
+ query_service: QueryService
19
+ ):
20
+ self.iauthentication = iauthentication
21
+ self.query_service = query_service
22
+
23
+ def get(self, company_short_name: str, external_user_id: str):
24
+ # 1. get access credentials
25
+ iaut = self.iauthentication.verify(company_short_name, external_user_id)
26
+ if not iaut.get("success"):
27
+ return jsonify(iaut), 401
28
+
29
+ try:
30
+ # initialize the context
31
+ self.query_service.llm_init_context(
32
+ company_short_name=company_short_name,
33
+ external_user_id=external_user_id
34
+ )
35
+
36
+ return {'status': 'OK'}, 200
37
+ except Exception as e:
38
+ logging.exception(
39
+ f"Error inesperado al inicializar el contexto durante el login para company {company_short_name}: {e}")
40
+ return jsonify({"error_message": str(e)}), 500
@@ -0,0 +1,58 @@
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 services.load_documents_service import LoadDocumentsService
9
+ from repositories.profile_repo import ProfileRepo
10
+ from injector import inject
11
+ import base64
12
+
13
+
14
+ class FileStoreView(MethodView):
15
+ @inject
16
+ def __init__(self,
17
+ doc_service: LoadDocumentsService,
18
+ profile_repo: ProfileRepo,):
19
+ self.doc_service = doc_service
20
+ self.profile_repo = profile_repo
21
+
22
+ def post(self):
23
+ try:
24
+ req_data = request.get_json()
25
+
26
+ required_fields = ['company', 'filename', 'content']
27
+ for field in required_fields:
28
+ if field not in req_data:
29
+ return jsonify({"error": f"El campo {field} es requerido"}), 400
30
+
31
+ company_short_name = req_data.get('company', '')
32
+ filename = req_data.get('filename', False)
33
+ base64_content = req_data.get('content', '')
34
+ metadata = req_data.get('metadata', {})
35
+
36
+ # get company
37
+ company = self.profile_repo.get_company_by_short_name(company_short_name)
38
+ if not company:
39
+ return jsonify({"error": f"La empresa {company_short_name} no existe"}), 400
40
+
41
+ # get the file content from base64
42
+ content = base64.b64decode(base64_content)
43
+
44
+ new_document = self.doc_service.load_file_callback(
45
+ filename=filename,
46
+ content=content,
47
+ company=company,
48
+ context={'metadata': metadata})
49
+
50
+ return jsonify({
51
+ "document_id": new_document.id,
52
+ }), 200
53
+
54
+ except Exception as e:
55
+ response = jsonify({"error": str(e)})
56
+ response.status_code = 500
57
+
58
+ return response
@@ -0,0 +1,64 @@
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 render_template, request, url_for
8
+ from injector import inject
9
+ from services.profile_service import ProfileService
10
+ from itsdangerous import URLSafeTimedSerializer
11
+ import os
12
+
13
+ class ForgotPasswordView(MethodView):
14
+ @inject
15
+ def __init__(self, profile_service: ProfileService):
16
+ self.profile_service = profile_service
17
+ self.serializer = URLSafeTimedSerializer(os.getenv("PASS_RESET_KEY"))
18
+
19
+ def get(self, company_short_name: str):
20
+ # get company info
21
+ company = self.profile_service.get_company_by_short_name(company_short_name)
22
+ if not company:
23
+ return render_template('error.html', message="Empresa no encontrada"), 404
24
+
25
+ return render_template('forgot_password.html',
26
+ company=company,
27
+ company_short_name=company_short_name
28
+ )
29
+
30
+ def post(self, company_short_name: str):
31
+ company = self.profile_service.get_company_by_short_name(company_short_name)
32
+ if not company:
33
+ return render_template('error.html', message="Empresa no encontrada"), 404
34
+
35
+ try:
36
+ email = request.form.get('email')
37
+
38
+ # create a safe token and url for it
39
+ token = self.serializer.dumps(email, salt='password-reset')
40
+ reset_url = url_for('change_password',
41
+ company_short_name=company_short_name,
42
+ token=token, _external=True)
43
+
44
+ response = self.profile_service.forgot_password(email=email, reset_url=reset_url)
45
+ if "error" in response:
46
+ return render_template(
47
+ 'forgot_password.html',
48
+ company=company,
49
+ company_short_name=company_short_name,
50
+ form_data={"email": email },
51
+ alert_message=response["error"]), 400
52
+
53
+
54
+ return render_template('login.html',
55
+ company=company,
56
+ company_short_name=company_short_name,
57
+ alert_icon='success',
58
+ alert_message="Hemos enviado un enlace a tu correo para restablecer la contraseña.")
59
+ except Exception as e:
60
+ return render_template("error.html",
61
+ company=company,
62
+ company_short_name=company_short_name,
63
+ message="Ha ocurrido un error inesperado."), 500
64
+
views/history_view.py ADDED
@@ -0,0 +1,57 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask import request, jsonify, render_template
7
+ from flask.views import MethodView
8
+ from services.history_service import HistoryService
9
+ from common.auth import IAuthentication
10
+ from injector import inject
11
+ import logging
12
+
13
+
14
+ class HistoryView(MethodView):
15
+ @inject
16
+ def __init__(self,
17
+ iauthentication: IAuthentication,
18
+ history_service: HistoryService ):
19
+ self.iauthentication = iauthentication
20
+ self.history_service = history_service
21
+
22
+ def post(self, company_short_name):
23
+ try:
24
+ data = request.get_json()
25
+ except Exception:
26
+ return jsonify({"error_message": "Cuerpo de la solicitud JSON inválido o faltante"}), 400
27
+
28
+ if not data:
29
+ return jsonify({"error_message": "Cuerpo de la solicitud JSON inválido o faltante"}), 400
30
+
31
+ # get access credentials
32
+ iaut = self.iauthentication.verify(company_short_name, data.get("external_user_id"))
33
+ if not iaut.get("success"):
34
+ return jsonify(iaut), 401
35
+
36
+ external_user_id = data.get("external_user_id")
37
+ local_user_id = data.get("local_user_id", 0)
38
+
39
+ try:
40
+ response = self.history_service.get_history(
41
+ company_short_name=company_short_name,
42
+ external_user_id=external_user_id,
43
+ local_user_id=local_user_id
44
+ )
45
+
46
+ if "error" in response:
47
+ return {'error_message': response["error"]}, 402
48
+
49
+ return response, 200
50
+ except Exception as e:
51
+ logging.exception(
52
+ f"Error inesperado al obtener el historial de consultas para company {company_short_name}: {e}")
53
+ if local_user_id:
54
+ return render_template("error.html",
55
+ message="Ha ocurrido un error inesperado."), 500
56
+ else:
57
+ return jsonify({"error_message": str(e)}), 500
views/home_view.py ADDED
@@ -0,0 +1,34 @@
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 render_template, request
8
+ from injector import inject
9
+ from services.profile_service import ProfileService
10
+ import os
11
+
12
+
13
+ class HomeView(MethodView):
14
+ @inject
15
+ def __init__(self,
16
+ profile_service: ProfileService):
17
+ self.profile_service = profile_service
18
+
19
+ def get(self):
20
+ user_agent = request.user_agent
21
+ is_mobile = user_agent.platform in ["android", "iphone", "ipad"] or "mobile" in user_agent.string.lower()
22
+ alert_message = request.args.get('alert_message', None)
23
+ companies = self.profile_service.get_companies()
24
+
25
+ # Esta API_KEY para el login
26
+ api_key_for_login = os.getenv("IATOOLKIT_API_KEY", "tu_api_key_por_defecto_o_error")
27
+
28
+ return render_template('home.html',
29
+ companies=companies,
30
+ is_mobile=is_mobile,
31
+ alert_message=alert_message,
32
+ alert_icon='success' if alert_message else None,
33
+ api_key=api_key_for_login
34
+ )