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.
- iatoolkit/__init__.py +13 -35
- iatoolkit/base_company.py +74 -8
- iatoolkit/cli_commands.py +15 -23
- iatoolkit/common/__init__.py +0 -0
- iatoolkit/common/exceptions.py +46 -0
- iatoolkit/common/routes.py +141 -0
- iatoolkit/common/session_manager.py +24 -0
- iatoolkit/common/util.py +348 -0
- iatoolkit/company_registry.py +7 -8
- iatoolkit/iatoolkit.py +169 -96
- iatoolkit/infra/__init__.py +5 -0
- iatoolkit/infra/call_service.py +140 -0
- iatoolkit/infra/connectors/__init__.py +5 -0
- iatoolkit/infra/connectors/file_connector.py +17 -0
- iatoolkit/infra/connectors/file_connector_factory.py +57 -0
- iatoolkit/infra/connectors/google_cloud_storage_connector.py +53 -0
- iatoolkit/infra/connectors/google_drive_connector.py +68 -0
- iatoolkit/infra/connectors/local_file_connector.py +46 -0
- iatoolkit/infra/connectors/s3_connector.py +33 -0
- iatoolkit/infra/gemini_adapter.py +356 -0
- iatoolkit/infra/google_chat_app.py +57 -0
- iatoolkit/infra/llm_client.py +429 -0
- iatoolkit/infra/llm_proxy.py +139 -0
- iatoolkit/infra/llm_response.py +40 -0
- iatoolkit/infra/mail_app.py +145 -0
- iatoolkit/infra/openai_adapter.py +90 -0
- iatoolkit/infra/redis_session_manager.py +122 -0
- iatoolkit/locales/en.yaml +144 -0
- iatoolkit/locales/es.yaml +140 -0
- iatoolkit/repositories/__init__.py +5 -0
- iatoolkit/repositories/database_manager.py +110 -0
- iatoolkit/repositories/document_repo.py +33 -0
- iatoolkit/repositories/llm_query_repo.py +91 -0
- iatoolkit/repositories/models.py +336 -0
- iatoolkit/repositories/profile_repo.py +123 -0
- iatoolkit/repositories/tasks_repo.py +52 -0
- iatoolkit/repositories/vs_repo.py +139 -0
- iatoolkit/services/__init__.py +5 -0
- iatoolkit/services/auth_service.py +193 -0
- {services → iatoolkit/services}/benchmark_service.py +6 -6
- iatoolkit/services/branding_service.py +149 -0
- {services → iatoolkit/services}/dispatcher_service.py +39 -99
- {services → iatoolkit/services}/document_service.py +5 -5
- {services → iatoolkit/services}/excel_service.py +27 -21
- {services → iatoolkit/services}/file_processor_service.py +5 -5
- iatoolkit/services/help_content_service.py +30 -0
- {services → iatoolkit/services}/history_service.py +8 -16
- iatoolkit/services/i18n_service.py +104 -0
- {services → iatoolkit/services}/jwt_service.py +18 -27
- iatoolkit/services/language_service.py +77 -0
- {services → iatoolkit/services}/load_documents_service.py +19 -14
- {services → iatoolkit/services}/mail_service.py +5 -5
- iatoolkit/services/onboarding_service.py +43 -0
- {services → iatoolkit/services}/profile_service.py +155 -89
- {services → iatoolkit/services}/prompt_manager_service.py +26 -11
- {services → iatoolkit/services}/query_service.py +142 -104
- {services → iatoolkit/services}/search_service.py +21 -5
- {services → iatoolkit/services}/sql_service.py +24 -6
- {services → iatoolkit/services}/tasks_service.py +10 -10
- iatoolkit/services/user_feedback_service.py +103 -0
- iatoolkit/services/user_session_context_service.py +143 -0
- iatoolkit/static/images/fernando.jpeg +0 -0
- iatoolkit/static/js/chat_feedback_button.js +80 -0
- iatoolkit/static/js/chat_filepond.js +85 -0
- iatoolkit/static/js/chat_help_content.js +124 -0
- iatoolkit/static/js/chat_history_button.js +112 -0
- iatoolkit/static/js/chat_logout_button.js +36 -0
- iatoolkit/static/js/chat_main.js +364 -0
- iatoolkit/static/js/chat_onboarding_button.js +97 -0
- iatoolkit/static/js/chat_prompt_manager.js +94 -0
- iatoolkit/static/js/chat_reload_button.js +35 -0
- iatoolkit/static/styles/chat_iatoolkit.css +592 -0
- iatoolkit/static/styles/chat_modal.css +169 -0
- iatoolkit/static/styles/chat_public.css +107 -0
- iatoolkit/static/styles/landing_page.css +182 -0
- iatoolkit/static/styles/llm_output.css +115 -0
- iatoolkit/static/styles/onboarding.css +169 -0
- iatoolkit/system_prompts/query_main.prompt +5 -15
- iatoolkit/templates/_company_header.html +20 -0
- iatoolkit/templates/_login_widget.html +42 -0
- iatoolkit/templates/about.html +13 -0
- iatoolkit/templates/base.html +65 -0
- iatoolkit/templates/change_password.html +66 -0
- iatoolkit/templates/chat.html +287 -0
- iatoolkit/templates/chat_modals.html +181 -0
- iatoolkit/templates/error.html +51 -0
- iatoolkit/templates/forgot_password.html +50 -0
- iatoolkit/templates/index.html +145 -0
- iatoolkit/templates/login_simulation.html +34 -0
- iatoolkit/templates/onboarding_shell.html +104 -0
- iatoolkit/templates/signup.html +76 -0
- iatoolkit/views/__init__.py +5 -0
- iatoolkit/views/base_login_view.py +92 -0
- iatoolkit/views/change_password_view.py +117 -0
- iatoolkit/views/external_login_view.py +73 -0
- iatoolkit/views/file_store_api_view.py +65 -0
- iatoolkit/views/forgot_password_view.py +72 -0
- iatoolkit/views/help_content_api_view.py +54 -0
- iatoolkit/views/history_api_view.py +56 -0
- iatoolkit/views/home_view.py +61 -0
- iatoolkit/views/index_view.py +14 -0
- iatoolkit/views/init_context_api_view.py +73 -0
- iatoolkit/views/llmquery_api_view.py +57 -0
- iatoolkit/views/login_simulation_view.py +81 -0
- iatoolkit/views/login_view.py +153 -0
- iatoolkit/views/logout_api_view.py +49 -0
- iatoolkit/views/profile_api_view.py +46 -0
- iatoolkit/views/prompt_api_view.py +37 -0
- iatoolkit/views/signup_view.py +94 -0
- iatoolkit/views/tasks_api_view.py +72 -0
- iatoolkit/views/tasks_review_api_view.py +55 -0
- iatoolkit/views/user_feedback_api_view.py +60 -0
- iatoolkit/views/verify_user_view.py +62 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/METADATA +2 -2
- iatoolkit-0.66.2.dist-info/RECORD +119 -0
- {iatoolkit-0.4.2.dist-info → iatoolkit-0.66.2.dist-info}/top_level.txt +0 -1
- iatoolkit/system_prompts/arquitectura.prompt +0 -32
- iatoolkit-0.4.2.dist-info/RECORD +0 -32
- services/__init__.py +0 -5
- services/api_service.py +0 -75
- services/user_feedback_service.py +0 -67
- services/user_session_context_service.py +0 -85
- {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.
|
|
3
|
+
Version: 0.66.2
|
|
4
4
|
Summary: IAToolkit
|
|
5
5
|
Author: Fernando Libedinsky
|
|
6
6
|
License-Expression: MIT
|
|
7
|
-
Requires-Python: >=3.
|
|
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
|