Coverage for src/app/main.py: 48%
296 statements
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-03 00:42 +0200
« prev ^ index » next coverage.py v7.7.0, created at 2025-04-03 00:42 +0200
1from flask import Flask, request, jsonify
2from flask_cors import CORS
3from flask_jwt_extended import create_access_token, jwt_required, get_jwt_identity, get_jwt
4from flask import Flask, request, jsonify
6import os
7import fitz
8import pytz
9from datetime import datetime
11from utils.midleware_admin import admin_required
13from dependencies.encoding import detect_encoding
14from dependencies.dependency_inj import dependency_injection
16from dto.answer_dto import AnswerDTO
17from dto.question_dto import QuestionDTO
18from dto.file_dto import FileDTO
19from dto.conversation_dto import ConversationDTO
20from dto.message_dto import MessageDTO
21from dto.support_message_dto import SupportMessageDTO
22from dto.template_dto import TemplateDTO
23from dto.user_dto import UserDTO
26italy_tz = pytz.timezone('Europe/Rome')
28BASE_DIR = os.path.dirname(os.path.abspath(__file__))
29UPLOAD_FOLDER = os.path.join(BASE_DIR, "uploads")
30os.makedirs(UPLOAD_FOLDER, exist_ok=True)
32app = Flask(__name__)
33CORS(app) # Enable CORS for all routes
35# Initialize dependencies
36dependencies = dependency_injection(app)
38chat_controller = dependencies["chat_controller"]
39add_file_controller = dependencies["add_file_controller"]
41get_conversation_controller = dependencies["get_conversation_controller"]
42get_conversations_controller = dependencies["get_conversations_controller"]
43save_conversation_title_controller = dependencies["save_conversation_title_controller"]
44delete_conversation_controller = dependencies["delete_conversation_controller"]
46get_message_controller = dependencies["get_message_controller"]
47get_messages_by_conversation_controller = dependencies["get_messages_by_conversation_controller"]
48save_message_controller = dependencies["save_message_controller"]
50get_support_message_controller = dependencies["get_support_message_controller"]
51get_support_messages_controller = dependencies["get_support_messages_controller"]
52save_support_message_controller = dependencies["save_support_message_controller"]
53mark_done_support_message_controller = dependencies["mark_done_support_message_controller"]
54update_message_rating_controller = dependencies["update_message_rating_controller"]
55get_dashboard_metrics_controller = dependencies["get_dashboard_metrics_controller"]
57delete_template_controller = dependencies["delete_template_controller"]
58get_template_controller = dependencies["get_template_controller"]
59get_template_list_controller = dependencies["get_template_list_controller"]
60save_template_controller = dependencies["save_template_controller"]
62registration_controller = dependencies["registration_controller"]
63authentication_controller = dependencies["authentication_controller"]
65# ---- Authentication Routes ----
67@app.route("/register", methods=["POST"])
68def register():
69 """
70 curl -X POST http://127.0.0.1:5001/register \
71 -H "Content-Type: application/json" \
72 -d '{"username": "john", "password": "secret", "email": "john@example.com", "phone": "1234567890", "first_name": "John", "last_name": "Doe"}'
73 """
74 try:
75 data = request.json
77 username = data.get("username")
78 password = data.get("password")
79 email = data.get("email")
80 phone = data.get("phone", "")
81 first_name = data.get("first_name", "")
82 last_name = data.get("last_name", "")
84 user_dto = UserDTO(
85 username=username,
86 password=password,
87 email=email,
88 phone=phone,
89 first_name=first_name,
90 last_name=last_name,
91 is_admin=False
92 )
94 registration_controller.register(user_dto)
96 return jsonify({"message": "User registered successfully"}), 200
98 except Exception as e:
99 return jsonify({"error": str(e)}), 500
101@app.route("/login", methods=["POST"])
102def login():
103 """
104 curl -X POST http://127.0.0.1:5001/login \
105 -H "Content-Type: application/json" \
106 -d '{"username": "johnny", "password": "secret"}'
107 """
108 try:
109 data = request.json
110 username = data.get("username")
111 password = data.get("password")
113 user_dto = UserDTO(
114 username=username,
115 password=password
116 )
118 user_result = authentication_controller.login(user_dto)
120 access_token = create_access_token(identity=str(user_result.get_id()), additional_claims={"is_admin": user_result.get_is_admin()})
122 return jsonify({
123 "access_token": access_token,
124 "user": {
125 "username": user_result.get_username(),
126 "email": user_result.get_email(),
127 "phone": user_result.get_phone(),
128 "first_name": user_result.get_first_name(),
129 "last_name": user_result.get_last_name()
130 }
131 }), 200
133 except Exception as e:
134 return jsonify({"error": str(e)}), 500
136@app.route("/is_admin", methods=["GET"])
137@jwt_required()
138def is_admin():
139 """
140 Endpoint to check if the user associated with the provided token is an admin.
141 curl -X GET http://127.0.0.1:5001/is_admin \
142 -H "Authorization: Bearer <your_token>"
143 """
144 try:
145 claims = get_jwt()
146 is_admin = claims.get("is_admin", False)
147 return jsonify({"is_admin": is_admin}), 200
148 except Exception as e:
149 return jsonify({"error": str(e)}), 500
151# ---- Conversation Routes ----
152@app.route("/conversation/get/<int:conversation_id>", methods=["GET"])
153@jwt_required()
154def get_conversation(conversation_id):
155 """
156 # To test this endpoint with curl:
157 curl -X GET http://127.0.0.1:5001/conversation/get/<conversation_id> \
158 -H "Authorization: Bearer <your_token>"
160 """
161 conversation = ConversationDTO(
162 id=conversation_id
163 )
165 try:
166 conversation_result = get_conversation_controller.get_conversation(conversation)
167 except Exception as e:
168 return jsonify({"error": str(e)}), 500
170 return jsonify({
171 "id": conversation_result.get_id(),
172 "title": conversation_result.get_title()
173 }), 200
176@app.route("/conversation/get_all", methods=["GET"])
177@jwt_required()
178def get_conversations():
179 """
180 # To test this endpoint with curl:
181 curl -X GET http://127.0.0.1:5001/conversation/get_all \
182 -H "Authorization: Bearer <your_token>"
183 """
184 try:
185 user_id = int(get_jwt_identity())
187 conversation = ConversationDTO(
188 user_id=user_id
189 )
191 conversations = get_conversations_controller.get_conversations(conversation)
192 except Exception as e:
193 return jsonify({"error": str(e)}), 500
195 return jsonify([{
196 "id": conversation.get_id(),
197 "title": conversation.get_title(),
198 "user_id": conversation.get_user_id()
199 } for conversation in conversations]), 200
202@app.route("/conversation/save_title", methods=["POST"])
203@jwt_required()
204def save_conversation_title():
205 try:
206 data = request.get_json()
207 # print("Received data:", data) # Debugging log
208 user = int(get_jwt_identity())
209 # print("User ID:", user) # Debugging log
211 conversation = ConversationDTO(
212 title=data.get("title"),
213 user_id=user
214 )
215 saved_id = save_conversation_title_controller.save_conversation_title(conversation)
216 except Exception as e:
217 # print("Error:", str(e)) # Debugging log
218 return jsonify({"error": str(e)}), 500
220 return jsonify({"message": f"Conversation title saved with id: {saved_id}"}), 200
223@app.route("/conversation/delete/<int:conversation_id>", methods=["DELETE"])
224@jwt_required()
225def delete_conversation(conversation_id):
226 """
227 Endpoint to delete a conversation by its ID.
228 curl -X DELETE http://127.0.0.1:5001/conversation/delete/<conversation_id> \
229 -H "Authorization: Bearer <your_token>"
231 API Call:
232 - Method: DELETE
233 - URL: /conversation/delete/<conversation_id>
234 - Requires JWT Authorization.
236 Possible Responses:
237 - 200 OK: {"message": "Conversation deleted successfully"}
238 - 500 Internal Server Error: {"error": "<error message>"} (if an internal error occurs)
240 Functionality:
241 - Validates the conversation ID.
242 - Calls the controller to delete the conversation.
243 - Returns a success message or an error in case of issues.
244 """
245 try:
246 user_id = int(get_jwt_identity()) # Get the user ID from the JWT token
248 # Create a DTO for the conversation
249 conversation = ConversationDTO(
250 id=conversation_id,
251 user_id=user_id
252 )
254 # Call the controller to delete the conversation
255 delete_conversation_controller.delete_conversation(conversation)
257 return jsonify({"message": "Conversation deleted successfully"}), 200
258 except Exception as e:
259 return jsonify({"error": str(e)}), 500
261# ---- Message Routes ----
262@app.route("/message/get/<int:message_id>", methods=["GET"])
263@jwt_required()
264def get_message(message_id):
265 """
266 # To test this endpoint with curl:
267 curl -X GET http://127.0.0.1:5001/message/get/<message_id> \
268 -H "Authorization: Bearer <your_token>"
269 """
271 message = MessageDTO(
272 id=message_id
273 )
275 try:
276 message_result = get_message_controller.get_message(message)
277 except Exception as e:
278 return jsonify({"error": str(e)}), 500
280 return jsonify({
281 "id": message_result.get_id(),
282 "text": message_result.get_text(),
283 "is_bot": message_result.get_is_bot(),
284 "conversation_id": message_result.get_conversation_id(),
285 "rating": message_result.get_rating(),
286 "created_at": message_result.get_created_at()
287 }), 200
290@app.route("/message/get_by_conversation/<int:conversation_id>", methods=["GET"])
291@jwt_required()
292def get_messages_by_conversation(conversation_id):
293 """
294 # To test this endpoint with curl:
295 curl -X GET http://127.0.0.1:5001/message/get_by_conversation/<conversation_id> \
296 -H "Authorization: Bearer <your_token>"
297 """
299 message = MessageDTO(
300 conversation_id=conversation_id
301 )
303 try:
304 messages_result = get_messages_by_conversation_controller.get_messages_by_conversation(message)
306 except Exception as e:
307 return jsonify({"error": str(e)}), 500
309 return jsonify([{
310 "id": message.get_id(),
311 "text": message.get_text(),
312 "is_bot": message.get_is_bot(),
313 "conversation_id": message.get_conversation_id(),
314 "rating": message.get_rating(),
315 "created_at": message.get_created_at()
316 } for message in messages_result]), 200
319@app.route("/message/save", methods=["POST"])
320@jwt_required()
321def save_message():
322 """
323 # To test this endpoint with curl:
324 curl -X POST http://127.0.0.1:5001/message/save \
325 -H "Content-Type: application/json" \
326 -d '{"text": "Message text", "conversation_id": 2, "rating": true, "is_bot": false}' \
327 -H "Authorization: Bearer <your_token>"
328 """
329 data = request.get_json()
331 # Create DTO
332 message = MessageDTO(
333 text=data.get("text"),
334 conversation_id=data.get("conversation_id"),
335 rating=data.get("rating"),
336 is_bot=data.get("is_bot"),
337 created_at=datetime.now(italy_tz)
338 )
340 try:
341 saved_id = save_message_controller.save_message(message)
342 except Exception as e:
343 return jsonify({"error": str(e)}), 500
345 return jsonify({"message": f"Message saved with id: {saved_id}"}), 200
347@app.route("/message/update_rating", methods=["POST"])
348@jwt_required()
349def update_message_rating():
350 data = request.get_json()
352 message = MessageDTO(
353 id=data.get("id"),
354 rating=data.get("rating")
355 )
357 try:
358 result = update_message_rating_controller.update_message_rating(message)
359 except Exception as e:
360 return jsonify({"error": str(e)}), 500
362 return jsonify({"message": f"Message rating updated with id: {message.get_id()}"}), 200
364@app.route("/dashboard/metrics", methods=["GET"])
365@admin_required
366def get_dashboard_metrics():
367 """
368 Endpoint to calculate and return dashboard metrics.
369 curl -X GET http://127.0.0.1:5001/dashboard/metrics \
370 -H "Authorization: Bearer <your_token>"
371 """
372 try:
374 metrics_dto = get_dashboard_metrics_controller.get_dashboard_metrics()
376 return jsonify({
377 "total_likes": metrics_dto.get_total_likes(),
378 "total_dislikes": metrics_dto.get_total_dislikes(),
379 "total_messages": metrics_dto.get_total_messages(),
380 "positive_rating": metrics_dto.get_positive_rating()
381 }), 200
383 except Exception as e:
384 return jsonify({"error": str(e)}), 500
387# ---- Support Message Routes ----
388@app.route("/support_message/get/<int:support_message_id>", methods=["GET"])
389@admin_required
390def get_support_message(support_message_id):
391 """
392 # To test this endpoint with curl:
393 curl -X GET http://127.0.0.1:5001/support_message/get/<support_message_id> \
394 -H "Authorization: Bearer <your_token>"
395 """
397 support_message_dto = SupportMessageDTO(
398 id=support_message_id
399 )
401 try:
402 support_message = get_support_message_controller.get_support_message(support_message_dto)
403 except Exception as e:
404 return jsonify({"error": str(e)}), 500
406 return jsonify({
407 "id": support_message.get_id(),
408 "user_id": support_message.get_user_id(),
409 "description": support_message.get_description(),
410 "status": support_message.get_status(),
411 "subject": support_message.get_subject(),
412 "created_at": support_message.get_created_at()
413 }), 200
416@app.route("/support_message/get_all", methods=["GET"])
417@admin_required
418def get_support_messages():
419 """
420 # To test this endpoint with curl:
421 curl -X GET http://127.0.0.1:5001/support_message/get_all \
422 -H "Authorization: Bearer <your_token>"
423 """
424 try:
425 support_messages = get_support_messages_controller.get_support_messages()
426 except Exception as e:
427 return jsonify({"error": str(e)}), 500
429 return jsonify([{
430 "id": message.get_id(),
431 "user_id": message.get_user_id(),
432 "user_email": message.get_user_email(),
433 "description": message.get_description(),
434 "status": message.get_status(),
435 "subject": message.get_subject(),
436 "created_at": message.get_created_at()
437 } for message in support_messages]), 200
440@app.route("/support_message/save", methods=["POST"])
441@jwt_required()
442def save_support_message():
443 """
444 # To test this endpoint with curl:
445 curl -X POST http://127.0.0.1:5001/support_message/save \
446 -H "Content-Type: application/json" \
447 -d '{"description": "Support message description", "status": "true", "subject": "Support subject"}' \
448 -H "Authorization: Bearer <your_token>"
449 """
450 data = request.get_json()
451 user_id = int(get_jwt_identity())
453 # Create DTO
454 support_message = SupportMessageDTO(
455 user_id=user_id,
456 description=data.get("description"),
457 status=False,
458 subject=data.get("subject"),
459 created_at=datetime.now(italy_tz)
460 )
462 try:
463 saved_id = save_support_message_controller.save_support_message(support_message)
464 except Exception as e:
465 return jsonify({"error": str(e)}), 500
467 return jsonify({"message": f"Support message saved with id: {saved_id}"}), 200
470@app.route("/support_message/mark_done/<int:support_message_id>", methods=["POST"])
471@admin_required
472def mark_support_message_done(support_message_id):
473 """
474 Endpoint to mark a support message as done.
475 curl -X POST http://127.0.0.1:5001/support_message/mark_done/<support_message_id> \
476 -H "Authorization: Bearer <your_token>"
477 """
478 try:
479 # Create DTO with the support message ID and status set to True
480 support_message_dto = SupportMessageDTO(
481 id=support_message_id,
482 status=True
483 )
484 # Call the controller to update the status
485 result = mark_done_support_message_controller.mark_done_support_messages(support_message_dto)
486 if result:
487 return jsonify({"message": f"Support message with id {result} marked as done"}), 200
488 else:
489 return jsonify({"error": f"Failed to mark support message with id {result} as done"}), 500
490 except Exception as e:
491 return jsonify({"error": str(e)}), 500
493# ---- Template Routes ----
494@app.route("/template/delete/<int:template_id>", methods=["DELETE"])
495@admin_required
496def delete_template(template_id):
497 """
498 # To test this endpoint with curl:
499 curl -X DELETE http://127.0.0.1:5001/template/delete/<template_id> \
500 -H "Authorization: Bearer <your_token>"
501 """
502 template_dto = TemplateDTO(
503 id=template_id
504 )
506 try:
507 result = delete_template_controller.delete_template(template_dto)
508 except Exception as e:
509 return jsonify({"error": str(e)}), 500
511 if result:
512 return jsonify({"message": f"Template with id {template_id} deleted successfully"}), 200
513 else:
514 return jsonify({"error": f"Failed to delete template with id {template_id}"}), 500
517@app.route("/template/get/<int:template_id>", methods=["GET"])
518@jwt_required()
519def get_template(template_id):
520 """
521 # To test this endpoint with curl:
522 curl -X GET http://127.0.0.1:5001/template/get/<template_id> \
523 -H "Authorization: Bearer <your_token>"
524 """
526 template_dto = TemplateDTO(
527 id=template_id
528 )
530 try:
531 template = get_template_controller.get_template(template_dto)
532 except Exception as e:
533 return jsonify({"error": str(e)}), 500
535 return jsonify({
536 "id": template.get_id(),
537 "question": template.get_question(),
538 "answer": template.get_answer(),
539 "author_id": template.get_author_id(),
540 "last_modified": template.get_last_modified()
541 }), 200
544@app.route("/template/get_list", methods=["GET"])
545@jwt_required()
546def get_template_list():
547 """
548 # To test this endpoint with curl:
549 curl -X GET http://127.0.0.1:5001/template/get_list \
550 -H "Authorization: Bearer <your_token>"
551 """
552 try:
553 templates = get_template_list_controller.get_template_list()
554 except Exception as e:
555 return jsonify({"error": str(e)}), 500
557 return jsonify([{
558 "id": template.get_id(),
559 "question": template.get_question(),
560 "answer": template.get_answer(),
561 "author_id": template.get_author_id(),
562 "last_modified": template.get_last_modified()
563 } for template in templates]), 200
566@app.route("/template/save", methods=["POST"])
567@admin_required
568def save_template():
569 """
570 # To test this endpoint with curl:
571 curl -X POST http://127.0.0.1:5001/template/save \
572 -H "Content-Type: application/json" \
573 -d '{"question": "Sample question", "answer": "Sample answer"}' \
574 -H "Authorization: Bearer <your_token>"
575 """
576 data = request.get_json()
577 user_id = int(get_jwt_identity())
579 # Create DTO
580 template = TemplateDTO(
581 question=data.get("question"),
582 answer=data.get("answer"),
583 author_id=user_id,
584 last_modified=datetime.now(italy_tz)
585 )
587 try:
588 saved_id = save_template_controller.save_template(template)
589 except Exception as e:
590 return jsonify({"error": str(e)}), 500
592 return jsonify({"message": f"Template saved with id: {saved_id}"}), 200
595@app.route("/api/add_file", methods=["POST"])
596@admin_required
597def add_file():
598 """Endpoint to upload a PDF or TXT file.
599 curl -X POST http://127.0.0.1:5001/api/add_file \
600 -H "Authorization: Bearer <your_token>" \
601 -F "file=@/path/to/your/file.pdf"
603 API Call:
604 - Method: POST
605 - URL: /api/add_file
606 - Request Body (multipart/form-data):
607 - "file": the file to upload (must be PDF or TXT)
609 Possible Responses:
610 - 200 OK: {"message": "File successfully uploaded"}
611 - 400 Bad Request: {"error": "No file part"} (if no file is provided)
612 - 400 Bad Request: {"error": "No selected file"} (if the file is not selected)
613 - 400 Bad Request: {"error": "Unsupported file type"} (if the file is not a PDF or TXT)
614 - 400 Bad Request: {"error": "File encoding not supported. Please use UTF-8."} (if a TXT file has unsupported encoding)
615 - 400 Bad Request: {"error": "Error reading PDF: <details>"} (if there is an issue reading the PDF)
617 Functionality:
618 - Checks if a file was uploaded correctly.
619 - Validates the file extension (.pdf or .txt).
620 - Saves the file in UPLOAD_FOLDER. (optional)
621 - Reads the file content and passes it to the controller via a DTO.
622 """
624 if 'file' not in request.files:
625 return jsonify({"error": "No file part"}), 400
627 file = request.files['file']
628 if file.filename == '':
629 return jsonify({"error": "No selected file"}), 400
631 # verify file type
632 if not (file.filename.endswith('.pdf') or file.filename.endswith('.txt')):
633 return jsonify({"error": "Unsupported file type"}), 400
635 # save file
636 file_path = os.path.join(UPLOAD_FOLDER, file.filename)
637 file.save(file_path)
639 # Read file content
640 if file.filename.endswith('.txt'):
641 file_encoding = detect_encoding(file_path)
642 try:
643 with open(file_path, 'r', encoding=file_encoding) as f:
644 file_content = f.read()
645 except UnicodeDecodeError:
646 return jsonify({"error": "File encoding not supported. Please use UTF-8."}), 400
648 elif file.filename.endswith('.pdf'):
649 try:
650 doc = fitz.open(file_path)
651 file_content = "\n".join([page.get_text() for page in doc])
652 except Exception as e:
653 return jsonify({"error": f"Error reading PDF: {str(e)}"}), 400
655 # DTO and controller
656 file_dto = FileDTO(file.filename, file_content)
657 add_file_controller.load_file(file_dto)
659 return jsonify({"message": "File successfully uploaded"}), 200
662@app.route("/api/chat_interact", methods=["POST"])
663@jwt_required()
664def chat():
665 """Chat endpoint to receive a question and return an answer.
666 curl -X POST http://localhost:5001/api/chat_interact \
667 -H "Content-Type: application/json" \
668 -d "{\"question\": \"parlami dell'olio che hai?\"}" \
669 -H "Authorization: Bearer <your_token>"
671 API Call:
672 - Method: POST
673 - URL: /api/chat_interact
674 - Request Body (JSON):
675 {
676 "question": "Question text"
677 }
679 Possible Responses:
680 - 200 OK: {"answer": "Generated assistant response"}
681 - 400 Bad Request: {"error": "Invalid input"} (if required fields are missing)
682 - 500 Internal Server Error: {"error": "<error message>"} (if an internal error occurs)
684 Functionality:
685 - Validates that the request JSON contains "user" and "question" fields.
686 - Creates a DTO with the user input.
687 - Calls the controller to generate a response.
688 - Returns the generated answer or an error in case of issues.
690 """
691 data = request.get_json()
692 user_id = int(get_jwt_identity())
694 # Validate input
695 if "question" not in data:
696 return jsonify({"error": "Invalid input"}), 400
698 question = data["question"]
700 # Create DTO and get response
701 user_input = QuestionDTO(user_id, question)
702 answer = chat_controller.get_answer(user_input)
704 try:
705 return jsonify({"answer": answer.get_answer()}), 200
706 except Exception as e:
707 return jsonify({"error": str(e)}), 500
709@app.errorhandler(400)
710def bad_request(error):
711 response = jsonify({"error": "Invalid input"})
712 response.status_code = 400
713 return response
716if __name__ == "__main__": 716 ↛ 717line 716 didn't jump to line 717 because the condition on line 716 was never true
717 app.run(host="0.0.0.0", port=5001, debug=True)