iatoolkit 0.4.2__py3-none-any.whl → 0.66.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 (123) hide show
  1. iatoolkit/__init__.py +13 -35
  2. iatoolkit/base_company.py +74 -8
  3. iatoolkit/cli_commands.py +15 -23
  4. iatoolkit/common/__init__.py +0 -0
  5. iatoolkit/common/exceptions.py +46 -0
  6. iatoolkit/common/routes.py +141 -0
  7. iatoolkit/common/session_manager.py +24 -0
  8. iatoolkit/common/util.py +348 -0
  9. iatoolkit/company_registry.py +7 -8
  10. iatoolkit/iatoolkit.py +169 -96
  11. iatoolkit/infra/__init__.py +5 -0
  12. iatoolkit/infra/call_service.py +140 -0
  13. iatoolkit/infra/connectors/__init__.py +5 -0
  14. iatoolkit/infra/connectors/file_connector.py +17 -0
  15. iatoolkit/infra/connectors/file_connector_factory.py +57 -0
  16. iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
  17. iatoolkit/infra/connectors/google_drive_connector.py +68 -0
  18. iatoolkit/infra/connectors/local_file_connector.py +46 -0
  19. iatoolkit/infra/connectors/s3_connector.py +33 -0
  20. iatoolkit/infra/gemini_adapter.py +356 -0
  21. iatoolkit/infra/google_chat_app.py +57 -0
  22. iatoolkit/infra/llm_client.py +429 -0
  23. iatoolkit/infra/llm_proxy.py +139 -0
  24. iatoolkit/infra/llm_response.py +40 -0
  25. iatoolkit/infra/mail_app.py +145 -0
  26. iatoolkit/infra/openai_adapter.py +90 -0
  27. iatoolkit/infra/redis_session_manager.py +122 -0
  28. iatoolkit/locales/en.yaml +144 -0
  29. iatoolkit/locales/es.yaml +140 -0
  30. iatoolkit/repositories/__init__.py +5 -0
  31. iatoolkit/repositories/database_manager.py +110 -0
  32. iatoolkit/repositories/document_repo.py +33 -0
  33. iatoolkit/repositories/llm_query_repo.py +91 -0
  34. iatoolkit/repositories/models.py +336 -0
  35. iatoolkit/repositories/profile_repo.py +123 -0
  36. iatoolkit/repositories/tasks_repo.py +52 -0
  37. iatoolkit/repositories/vs_repo.py +139 -0
  38. iatoolkit/services/__init__.py +5 -0
  39. iatoolkit/services/auth_service.py +193 -0
  40. {services → iatoolkit/services}/benchmark_service.py +6 -6
  41. iatoolkit/services/branding_service.py +149 -0
  42. {services → iatoolkit/services}/dispatcher_service.py +39 -99
  43. {services → iatoolkit/services}/document_service.py +5 -5
  44. {services → iatoolkit/services}/excel_service.py +27 -21
  45. {services → iatoolkit/services}/file_processor_service.py +5 -5
  46. iatoolkit/services/help_content_service.py +30 -0
  47. {services → iatoolkit/services}/history_service.py +8 -16
  48. iatoolkit/services/i18n_service.py +104 -0
  49. {services → iatoolkit/services}/jwt_service.py +18 -27
  50. iatoolkit/services/language_service.py +77 -0
  51. {services → iatoolkit/services}/load_documents_service.py +19 -14
  52. {services → iatoolkit/services}/mail_service.py +5 -5
  53. iatoolkit/services/onboarding_service.py +43 -0
  54. {services → iatoolkit/services}/profile_service.py +155 -89
  55. {services → iatoolkit/services}/prompt_manager_service.py +26 -11
  56. {services → iatoolkit/services}/query_service.py +142 -104
  57. {services → iatoolkit/services}/search_service.py +21 -5
  58. {services → iatoolkit/services}/sql_service.py +24 -6
  59. {services → iatoolkit/services}/tasks_service.py +10 -10
  60. iatoolkit/services/user_feedback_service.py +103 -0
  61. iatoolkit/services/user_session_context_service.py +143 -0
  62. iatoolkit/static/images/fernando.jpeg +0 -0
  63. iatoolkit/static/js/chat_feedback_button.js +80 -0
  64. iatoolkit/static/js/chat_filepond.js +85 -0
  65. iatoolkit/static/js/chat_help_content.js +124 -0
  66. iatoolkit/static/js/chat_history_button.js +112 -0
  67. iatoolkit/static/js/chat_logout_button.js +36 -0
  68. iatoolkit/static/js/chat_main.js +364 -0
  69. iatoolkit/static/js/chat_onboarding_button.js +97 -0
  70. iatoolkit/static/js/chat_prompt_manager.js +94 -0
  71. iatoolkit/static/js/chat_reload_button.js +35 -0
  72. iatoolkit/static/styles/chat_iatoolkit.css +592 -0
  73. iatoolkit/static/styles/chat_modal.css +169 -0
  74. iatoolkit/static/styles/chat_public.css +107 -0
  75. iatoolkit/static/styles/landing_page.css +182 -0
  76. iatoolkit/static/styles/llm_output.css +115 -0
  77. iatoolkit/static/styles/onboarding.css +169 -0
  78. iatoolkit/system_prompts/query_main.prompt +5 -15
  79. iatoolkit/templates/_company_header.html +20 -0
  80. iatoolkit/templates/_login_widget.html +42 -0
  81. iatoolkit/templates/about.html +13 -0
  82. iatoolkit/templates/base.html +65 -0
  83. iatoolkit/templates/change_password.html +66 -0
  84. iatoolkit/templates/chat.html +287 -0
  85. iatoolkit/templates/chat_modals.html +181 -0
  86. iatoolkit/templates/error.html +51 -0
  87. iatoolkit/templates/forgot_password.html +50 -0
  88. iatoolkit/templates/index.html +145 -0
  89. iatoolkit/templates/login_simulation.html +34 -0
  90. iatoolkit/templates/onboarding_shell.html +104 -0
  91. iatoolkit/templates/signup.html +76 -0
  92. iatoolkit/views/__init__.py +5 -0
  93. iatoolkit/views/base_login_view.py +92 -0
  94. iatoolkit/views/change_password_view.py +117 -0
  95. iatoolkit/views/external_login_view.py +73 -0
  96. iatoolkit/views/file_store_api_view.py +65 -0
  97. iatoolkit/views/forgot_password_view.py +72 -0
  98. iatoolkit/views/help_content_api_view.py +54 -0
  99. iatoolkit/views/history_api_view.py +56 -0
  100. iatoolkit/views/home_view.py +61 -0
  101. iatoolkit/views/index_view.py +14 -0
  102. iatoolkit/views/init_context_api_view.py +73 -0
  103. iatoolkit/views/llmquery_api_view.py +57 -0
  104. iatoolkit/views/login_simulation_view.py +81 -0
  105. iatoolkit/views/login_view.py +153 -0
  106. iatoolkit/views/logout_api_view.py +49 -0
  107. iatoolkit/views/profile_api_view.py +46 -0
  108. iatoolkit/views/prompt_api_view.py +37 -0
  109. iatoolkit/views/signup_view.py +94 -0
  110. iatoolkit/views/tasks_api_view.py +72 -0
  111. iatoolkit/views/tasks_review_api_view.py +55 -0
  112. iatoolkit/views/user_feedback_api_view.py +60 -0
  113. iatoolkit/views/verify_user_view.py +62 -0
  114. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
  115. iatoolkit-0.66.2.dist-info/RECORD +119 -0
  116. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
  117. iatoolkit/system_prompts/arquitectura.prompt +0 -32
  118. iatoolkit-0.4.2.dist-info/RECORD +0 -32
  119. services/__init__.py +0 -5
  120. services/api_service.py +0 -75
  121. services/user_feedback_service.py +0 -67
  122. services/user_session_context_service.py +0 -85
  123. {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,153 @@
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, redirect, render_template, url_for,
8
+ render_template_string, flash)
9
+ from injector import inject
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.onboarding_service import OnboardingService
16
+ from iatoolkit.services.i18n_service import I18nService
17
+ from iatoolkit.views.base_login_view import BaseLoginView
18
+ import logging
19
+
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
+ def post(self, company_short_name: str):
27
+ company = self.profile_service.get_company_by_short_name(company_short_name)
28
+ if not company:
29
+ return render_template('error.html',
30
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
31
+
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
+ 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.
63
+ try:
64
+ return self._handle_login_path(company, 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
+ onboarding_service: OnboardingService,
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.onboarding_service = onboarding_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.warning("Fallo crítico: 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)
123
+
124
+ # 2. Finalize the context rebuild (the heavy task).
125
+ self.query_service.finalize_context_rebuild(
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.onboarding_service.get_onboarding_cards(company)
133
+
134
+ # Get the entire 'js_messages' block in the correct language.
135
+ js_translations = self.i18n_service.get_translation_block('js_messages')
136
+
137
+ return render_template(
138
+ "chat.html",
139
+ 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
+ )
147
+
148
+ except Exception as e:
149
+ return render_template("error.html",
150
+ company_short_name=company_short_name,
151
+ branding=branding_data,
152
+ message=f"An unexpected error occurred during context loading: {str(e)}"), 500
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
@@ -0,0 +1,37 @@
1
+ # Copyright (c) 2024 Fernando Libedinsky
2
+ # Product: IAToolkit
3
+ #
4
+ # IAToolkit is open source software.
5
+
6
+ from flask import jsonify
7
+ from flask.views import MethodView
8
+ from iatoolkit.services.prompt_manager_service import PromptService
9
+ from iatoolkit.services.auth_service import AuthService
10
+ from injector import inject
11
+ import logging
12
+
13
+
14
+ class PromptApiView(MethodView):
15
+ @inject
16
+ def __init__(self,
17
+ auth_service: AuthService,
18
+ prompt_service: PromptService ):
19
+ self.auth_service = auth_service
20
+ self.prompt_service = prompt_service
21
+
22
+ def get(self, company_short_name):
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
+ response = self.prompt_service.get_user_prompts(company_short_name)
30
+ if "error" in response:
31
+ return {'error_message': response["error"]}, 402
32
+
33
+ return response, 200
34
+ except Exception as e:
35
+ logging.exception(
36
+ f"unexpected error getting company prompts: {e}")
37
+ return jsonify({"error_message": str(e)}), 500
@@ -0,0 +1,94 @@
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, redirect, flash
8
+ from iatoolkit.services.profile_service import ProfileService
9
+ from iatoolkit.services.branding_service import BrandingService
10
+ from iatoolkit.services.i18n_service import I18nService
11
+ from injector import inject
12
+ from itsdangerous import URLSafeTimedSerializer
13
+ import os
14
+
15
+
16
+ class SignupView(MethodView):
17
+ @inject
18
+ def __init__(self, profile_service: ProfileService,
19
+ branding_service: BrandingService,
20
+ i18n_service: I18nService):
21
+ self.profile_service = profile_service
22
+ self.branding_service = branding_service # 3. Guardar la instancia
23
+ self.i18n_service = i18n_service
24
+
25
+ self.serializer = URLSafeTimedSerializer(os.getenv("USER_VERIF_KEY"))
26
+
27
+
28
+ def get(self, company_short_name: str):
29
+ # get company info
30
+ company = self.profile_service.get_company_by_short_name(company_short_name)
31
+ if not company:
32
+ return render_template('error.html',
33
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
+
35
+ branding_data = self.branding_service.get_company_branding(company)
36
+ return render_template('signup.html',
37
+ company=company,
38
+ company_short_name=company_short_name,
39
+ branding=branding_data)
40
+
41
+ def post(self, company_short_name: str):
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)
49
+
50
+ first_name = request.form.get('first_name')
51
+ last_name = request.form.get('last_name')
52
+ email = request.form.get('email')
53
+ password = request.form.get('password')
54
+ confirm_password = request.form.get('confirm_password')
55
+
56
+ # create verification token and url for verification
57
+ token = self.serializer.dumps(email, salt='email-confirm')
58
+ verification_url = url_for('verify_account',
59
+ company_short_name=company_short_name,
60
+ token=token, _external=True)
61
+
62
+ response = self.profile_service.signup(
63
+ company_short_name=company_short_name,
64
+ email=email,
65
+ first_name=first_name, last_name=last_name,
66
+ password=password, confirm_password=confirm_password,
67
+ verification_url=verification_url)
68
+
69
+ if "error" in response:
70
+ flash(response["error"], 'error')
71
+ return render_template(
72
+ 'signup.html',
73
+ company=company,
74
+ company_short_name=company_short_name,
75
+ branding=branding_data,
76
+ form_data={
77
+ "first_name": first_name,
78
+ "last_name": last_name,
79
+ "email": email,
80
+ "password": password,
81
+ "confirm_password": confirm_password
82
+ }), 400
83
+
84
+ flash(response["message"], 'success')
85
+ return redirect(url_for('home', company_short_name=company_short_name))
86
+
87
+ except Exception as e:
88
+ message = self.i18n_service.t('errors.templates.processing_error', error=str(e))
89
+ return render_template(
90
+ "error.html",
91
+ company_short_name=company_short_name,
92
+ branding=branding_data,
93
+ message=message
94
+ ), 500
@@ -0,0 +1,72 @@
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
+ from datetime import datetime
13
+ import logging
14
+ from typing import Optional
15
+
16
+
17
+ class TaskApiView(MethodView):
18
+ @inject
19
+ def __init__(self,
20
+ auth_service: AuthService,
21
+ task_service: TaskService,
22
+ profile_repo: ProfileRepo):
23
+ self.auth_service = auth_service
24
+ self.task_service = task_service
25
+ self.profile_repo = profile_repo
26
+
27
+ def post(self):
28
+ try:
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")
32
+
33
+ req_data = request.get_json()
34
+ files = request.files.getlist('files')
35
+
36
+ required_fields = ['company', 'task_type', 'client_data']
37
+ for field in required_fields:
38
+ if field not in req_data:
39
+ return jsonify({"error": f"El campo {field} es requerido"}), 400
40
+
41
+ company_short_name = req_data.get('company', '')
42
+ task_type = req_data.get('task_type', '')
43
+ client_data = req_data.get('client_data', {})
44
+ company_task_id = req_data.get('company_task_id', 0)
45
+ execute_at = req_data.get('execute_at', None)
46
+
47
+ # validate date format is parameter is present
48
+ if execute_at:
49
+ try:
50
+ # date in iso format
51
+ execute_at = datetime.fromisoformat(execute_at)
52
+ except ValueError:
53
+ return jsonify({
54
+ "error": "El formato de execute_at debe ser YYYY-MM-DD HH:MM:SS"
55
+ }), 400
56
+
57
+ new_task = self.task_service.create_task(
58
+ company_short_name=company_short_name,
59
+ task_type_name=task_type,
60
+ client_data=client_data,
61
+ company_task_id=company_task_id,
62
+ execute_at=execute_at,
63
+ files=files)
64
+
65
+ return jsonify({
66
+ "task_id": new_task.id,
67
+ "status": new_task.status.name
68
+ }), 201
69
+
70
+ except Exception as e:
71
+ logging.exception("Error al crear la tarea: %s", str(e))
72
+ return jsonify({"error": str(e)}), 500
@@ -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
+
@@ -0,0 +1,62 @@
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, url_for, redirect, session, flash
8
+ from iatoolkit.services.profile_service import ProfileService
9
+ from itsdangerous import URLSafeTimedSerializer, SignatureExpired
10
+ from iatoolkit.services.branding_service import BrandingService
11
+ from iatoolkit.services.i18n_service import I18nService
12
+ from injector import inject
13
+ import os
14
+
15
+
16
+ class VerifyAccountView(MethodView):
17
+ @inject
18
+ def __init__(self,
19
+ profile_service: ProfileService,
20
+ branding_service: BrandingService,
21
+ i18n_service: I18nService):
22
+ self.profile_service = profile_service
23
+ self.branding_service = branding_service
24
+ self.i18n_service = i18n_service
25
+ self.serializer = URLSafeTimedSerializer(os.getenv("USER_VERIF_KEY"))
26
+
27
+ def get(self, company_short_name: str, token: str):
28
+ try:
29
+ # get company info
30
+ company = self.profile_service.get_company_by_short_name(company_short_name)
31
+ if not company:
32
+ return render_template('error.html',
33
+ message=self.i18n_service.t('errors.templates.company_not_found')), 404
34
+
35
+ branding_data = self.branding_service.get_company_branding(company)
36
+ try:
37
+ # decode the token from the URL
38
+ email = self.serializer.loads(token, salt='email-confirm', max_age=3600*5)
39
+ except SignatureExpired:
40
+ flash(self.i18n_service.t('errors.verification.token_expired'), 'error')
41
+ return render_template('signup.html',
42
+ company=company,
43
+ company_short_name=company_short_name,
44
+ branding=branding_data,
45
+ token=token), 400
46
+
47
+ response = self.profile_service.verify_account(email)
48
+ if "error" in response:
49
+ flash(response["error"], 'error')
50
+ return render_template(
51
+ 'signup.html',
52
+ company=company,
53
+ company_short_name=company_short_name,
54
+ branding=branding_data,
55
+ token=token), 400
56
+
57
+ flash(response['message'], 'success')
58
+ return redirect(url_for('home', company_short_name=company_short_name))
59
+
60
+ except Exception as e:
61
+ flash(self.i18n_service.t('errors.general.unexpected_error'), 'error')
62
+ return redirect(url_for('home', company_short_name=company_short_name))
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iatoolkit
3
- Version: 0.4.2
3
+ Version: 0.66.2
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