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