From 85898db60499d98449258d82e06a78955c323ae6 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sat, 31 May 2025 23:17:28 +0000 Subject: [PATCH 01/88] Refactor DynamoDB key attributes and update message saving parameters for consistency --- Jenkinsfile | 16 ++++++++++++++++ infrastructure/template.yaml | 6 +++--- src/dynamodb_repository.py | 4 ++-- src/main.py | 11 +---------- src/telegram_handler.py | 8 ++++---- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 3963f71..7a1de90 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -53,6 +53,14 @@ pipeline { } stage('Deploy') { + when { + anyOf { + branch 'matbradiouf' + branch 'dev' + branch 'preprod' + branch 'prod' + } + } steps { script { // Add your deployment commands here @@ -63,6 +71,14 @@ pipeline { } stage('Test endpoint'){ + when { + anyOf { + branch 'matbradiouf' + branch 'dev' + branch 'preprod' + branch 'prod' + } + } steps { script { // Add your endpoint testing commands here diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 9db70e3..c6dfcd2 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -23,7 +23,7 @@ Resources: TableName: !Sub "chatbot-dbtable-${EnvironmentName}" # Définition des attributs utilisés comme clés pour la table principale et les GSI AttributeDefinitions: - - AttributeName: "id" + - AttributeName: "chat_id" AttributeType: "S" - AttributeName: "timestamp" AttributeType: "S" @@ -31,7 +31,7 @@ Resources: AttributeType: "S" # Schéma de la clé primaire de la table principale KeySchema: - - AttributeName: "id" + - AttributeName: "chat_id" KeyType: "HASH" - AttributeName: "timestamp" KeyType: "RANGE" @@ -51,7 +51,7 @@ Resources: Projection: ProjectionType: "INCLUDE" NonKeyAttributes: # Attributs à projeter en plus des clés de l'index et de la table - - "id" + - "chat_id" - "message_id" - "role" - "text" diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 9365ddc..e167c65 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -46,7 +46,7 @@ async def save_message( try: item = { - 'id': str(chat_id), + 'chat_id': str(chat_id), 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'message_id': str(message_id), 'user_id': str(user_id), @@ -72,7 +72,7 @@ async def get_chat_history(self, chat_id: int, limit: int = 100) -> list[dict]: try: response = await asyncio.to_thread( self.table.query, - KeyConditionExpression=Key('id').eq(str(chat_id)), + KeyConditionExpression=Key('chat_id').eq(str(chat_id)), Limit=limit, ScanIndexForward=True # True pour tri ascendant (du plus ancien au plus récent) ) diff --git a/src/main.py b/src/main.py index f4f5a22..2b6c534 100644 --- a/src/main.py +++ b/src/main.py @@ -78,7 +78,7 @@ async def chat(question: str): ) Utils.log_info(chat_response) response = { - "id": { + "chat_id": { "S": f"{chat_response.id}", }, "question": { @@ -89,15 +89,6 @@ async def chat(question: str): } } Utils.insert_data(response) - await dynamodb_repo.save_message( - "7d6d6416cff4477082d884dbc1d50254", - "7d6d6416cff4477082d884dbc1d51293", - "mybot_id1354", - "toto_machin", - "user", - chat_response.choices[0].message.content, - "mistral-large-latest" - ) return response @app.post("/webhook", description="Endpoint pour recevoir les mises à jour ou changement dans le bot Telegram") diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 6b2addb..00c5d9c 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -36,7 +36,7 @@ async def start_command(update: Update, context): response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, "bot", response_text) + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") async def handle_message(update: Update, context): @@ -50,7 +50,7 @@ async def handle_message(update: Update, context): user_name = user.full_name or user.username or "N/A" # Enregistrer le message utilisateur via le repository - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") try: chat_response = mistral_client.chat.complete( @@ -71,8 +71,8 @@ async def handle_message(update: Update, context): bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, + response_text, "bot", - response_text, MISTRAL_MODEL ) except Exception as e: @@ -80,7 +80,7 @@ async def handle_message(update: Update, context): error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." # Enregistrer le message d'erreur du bot via le dépôt bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, "bot", error_response) + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") except Exception as e: Utils.log_error("Traitement du message échoué.") From bbe471bcb79c208be4ef1188d49c49e542eb98e4 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 00:17:59 +0000 Subject: [PATCH 02/88] Update environment variable credentials and default values in Jenkinsfile and template.yaml --- Jenkinsfile | 6 +++--- infrastructure/template.yaml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 7a1de90..bde5622 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -22,7 +22,7 @@ pipeline { stage('Environment variable injection') { steps { script { - withCredentials([file(credentialsId: 'matbradiouf-chatbot-env-file', variable: 'ENV_FILE')]) { + withCredentials([file(credentialsId: 'bradlab-chatbot-env-file', variable: 'ENV_FILE')]) { // Load the environment variables from the file echo "Loading environment variables from ${ENV_FILE}" sh "cat ${ENV_FILE} > .env" @@ -55,7 +55,7 @@ pipeline { stage('Deploy') { when { anyOf { - branch 'matbradiouf' + branch 'bradlab' branch 'dev' branch 'preprod' branch 'prod' @@ -73,7 +73,7 @@ pipeline { stage('Test endpoint'){ when { anyOf { - branch 'matbradiouf' + branch 'bradlab' branch 'dev' branch 'preprod' branch 'prod' diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index c6dfcd2..3b8dd4c 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -9,11 +9,11 @@ Parameters: Description: Environment name for the application dev/staging/production Type: String AllowedValues: - - matbradiouf + - bradlab - dev - preprod - prod - Default: matbradiouf + Default: bradlab ############################################################################### Resources: ############################################################################### From d22ac6d6f8e675d5c94f12a57b20aaf8d3acfe92 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 02:51:59 +0000 Subject: [PATCH 03/88] Add environment variables for Lambda function configuration in template.yaml --- infrastructure/template.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 3b8dd4c..a5c2d26 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -67,6 +67,18 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 + Environment: + Variables: + ENV_NAME: brad_test + AWS_REGION_NAME: eu-west-3 + DYNAMO_TABLE: chatbot-dbtable-bradlab + AWS_PROFILE: bradlab_profile + TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' + TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} + WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook + # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + # Secrets injectés dynamiquement + MISTRAL_API_KEY: '{{resolve:secretsmanager:chatbot-secrets:SecretString:MISTRAL_API_KEY}}' Events: Api: Type: HttpApi From 7c387dc553bab774f940b1bdfd257ada35a77310 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 03:05:33 +0000 Subject: [PATCH 04/88] Add .prod.env to .gitignore and comment out sensitive environment variables in template.yaml --- .gitignore | 1 + infrastructure/template.yaml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 22480a6..c8d023b 100644 --- a/.gitignore +++ b/.gitignore @@ -139,6 +139,7 @@ celerybeat.pid # Environments .env +.prod.env .venv env/ venv/ diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index a5c2d26..274ab49 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -73,12 +73,12 @@ Resources: AWS_REGION_NAME: eu-west-3 DYNAMO_TABLE: chatbot-dbtable-bradlab AWS_PROFILE: bradlab_profile - TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' + # TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" # Secrets injectés dynamiquement - MISTRAL_API_KEY: '{{resolve:secretsmanager:chatbot-secrets:SecretString:MISTRAL_API_KEY}}' + # MISTRAL_API_KEY: '{{resolve:secretsmanager:chatbot-secrets:SecretString:MISTRAL_API_KEY}}' Events: Api: Type: HttpApi From 4c6bfd19a67a3e017da636ee5639a2758d0db023 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 03:37:59 +0000 Subject: [PATCH 05/88] Update API_WEBHOOK_URL construction to include TELEGRAM_BOT_TOKEN for proper webhook configuration --- src/telegram_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 00c5d9c..00e1144 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -10,8 +10,8 @@ # Récupérer les jetons depuis les variables d'environnement TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY -# API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}/setWebhook?url={env_vars.WEBHOOK_URL}" -API_WEBHOOK_URL = env_vars.WEBHOOK_URL +API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={env_vars.WEBHOOK_URL}" +# API_WEBHOOK_URL = env_vars.WEBHOOK_URL MISTRAL_MODEL = "mistral-large-latest" if not TELEGRAM_BOT_TOKEN: From 121ac872b5273c9b5f145b7ef2d87d0f577f1990 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 03:56:45 +0000 Subject: [PATCH 06/88] Comment out TELEGRAM_API_URL and WEBHOOK_URL in template.yaml for sensitive information handling --- infrastructure/template.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 274ab49..a0c7776 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -74,8 +74,8 @@ Resources: DYNAMO_TABLE: chatbot-dbtable-bradlab AWS_PROFILE: bradlab_profile # TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' - TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} - WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook + # TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} + # WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" # Secrets injectés dynamiquement # MISTRAL_API_KEY: '{{resolve:secretsmanager:chatbot-secrets:SecretString:MISTRAL_API_KEY}}' From f1050795002aba44d0fee4669c19d88acc7b4c49 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 04:15:06 +0000 Subject: [PATCH 07/88] Comment out environment variables in template.yaml for sensitive information handling --- infrastructure/template.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index a0c7776..35286ad 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -67,12 +67,12 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - Environment: - Variables: - ENV_NAME: brad_test - AWS_REGION_NAME: eu-west-3 - DYNAMO_TABLE: chatbot-dbtable-bradlab - AWS_PROFILE: bradlab_profile + # Environment: + # Variables: + # ENV_NAME: brad_test + # AWS_REGION_NAME: eu-west-3 + # DYNAMO_TABLE: chatbot-dbtable-bradlab + # AWS_PROFILE: bradlab_profile # TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' # TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} # WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook From 22d462e2a605ea226cb9a717bd52396852d48eb7 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 11:00:58 +0000 Subject: [PATCH 08/88] Update environment variables in template.yaml for proper configuration --- infrastructure/template.yaml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 35286ad..e628370 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -67,18 +67,18 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - # Environment: - # Variables: - # ENV_NAME: brad_test - # AWS_REGION_NAME: eu-west-3 - # DYNAMO_TABLE: chatbot-dbtable-bradlab - # AWS_PROFILE: bradlab_profile - # TELEGRAM_BOT_TOKEN: '{{resolve:secretsmanager:chatbot-secrets:SecretString:TELEGRAM_BOT_TOKEN}}' - # TELEGRAM_API_URL: https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} + Environment: + Variables: + ENV_NAME: brad_test + AWS_REGION_NAME: eu-west-3 + DYNAMO_TABLE: chatbot-dbtable-bradlab + AWS_PROFILE: bradlab_profile + TELEGRAM_BOT_TOKEN: 7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c + TELEGRAM_API_URL: https://api.telegram.org/bot # WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook - # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" # Secrets injectés dynamiquement - # MISTRAL_API_KEY: '{{resolve:secretsmanager:chatbot-secrets:SecretString:MISTRAL_API_KEY}}' + MISTRAL_API_KEY: w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ Events: Api: Type: HttpApi From 3ebb69a35d0dbdf46802ed29925640131849bcaa Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 11:32:16 +0000 Subject: [PATCH 09/88] Comment out WEBHOOK_URL in template.yaml for sensitive information handling and update to a static URL --- infrastructure/template.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index e628370..493cdb9 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -76,7 +76,8 @@ Resources: TELEGRAM_BOT_TOKEN: 7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c TELEGRAM_API_URL: https://api.telegram.org/bot # WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook - WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + WEBHOOK_URL: !Sub "https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook" # Secrets injectés dynamiquement MISTRAL_API_KEY: w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ Events: From d51afef02ac81f765c4d83f6c69c7df5a5495919 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 12:00:35 +0000 Subject: [PATCH 10/88] Comment out environment variables in template.yaml for sensitive information handling --- infrastructure/template.yaml | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 493cdb9..0fbd8d9 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -67,19 +67,17 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - Environment: - Variables: - ENV_NAME: brad_test - AWS_REGION_NAME: eu-west-3 - DYNAMO_TABLE: chatbot-dbtable-bradlab - AWS_PROFILE: bradlab_profile - TELEGRAM_BOT_TOKEN: 7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c - TELEGRAM_API_URL: https://api.telegram.org/bot - # WEBHOOK_URL: !Sub ${TELEGRAM_API_URL}/setWebhook?url=https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook - # WEBHOOK_URL: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" - WEBHOOK_URL: !Sub "https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook" + # Environment: + # Variables: + # ENV_NAME: brad_test + # AWS_REGION_NAME: eu-west-3 + # DYNAMO_TABLE: chatbot-dbtable-bradlab + # AWS_PROFILE: bradlab_profile + # TELEGRAM_BOT_TOKEN: 7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c + # TELEGRAM_API_URL: https://api.telegram.org/bot + # WEBHOOK_URL: !Sub "https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook" # Secrets injectés dynamiquement - MISTRAL_API_KEY: w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ + # MISTRAL_API_KEY: w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ Events: Api: Type: HttpApi From 8d1a842cfa478c8dd41b478326980fa8ff72c03f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 16:09:25 +0000 Subject: [PATCH 11/88] Refactor environment variable handling in template.yaml for improved configuration and security --- infrastructure/template.yaml | 52 +++++++++++++++++++++++++++--------- src/config.py | 1 - 2 files changed, 39 insertions(+), 14 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 0fbd8d9..7d77780 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -15,6 +15,34 @@ Parameters: - prod Default: bradlab ############################################################################### + + AWSRegionName: + Description: AWS region where resources will be deployed (e.g. eu-west-3) + Type: String + Default: eu-west-3 + DynamoTable: + Description: Name of the DynamoDB table used to persist chatbot conversations + Type: String + NoEcho: true + # AwsProfile: + # Description: AWS CLI profile name used for deployment (for local/devops usage) + # Type: String + # Default: bradlab_profile + MistralApiKey: + Description: API key for accessing the Mistral AI service + Type: String + NoEcho: true + TelegramBotToken: + Description: Telegram bot token for authenticating with the Telegram API + Type: String + NoEcho: true + ApiWehookUrl: + Type: String + Default: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + TelegramApiBotUrl: + Type: String + Default: https://api.telegram.org/bot +############################################################################### Resources: ############################################################################### DynamoDBTable: @@ -67,22 +95,20 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - # Environment: - # Variables: - # ENV_NAME: brad_test - # AWS_REGION_NAME: eu-west-3 - # DYNAMO_TABLE: chatbot-dbtable-bradlab - # AWS_PROFILE: bradlab_profile - # TELEGRAM_BOT_TOKEN: 7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c - # TELEGRAM_API_URL: https://api.telegram.org/bot - # WEBHOOK_URL: !Sub "https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook" - # Secrets injectés dynamiquement - # MISTRAL_API_KEY: w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ + Environment: + Variables: + ENV_NAME: !Ref EnvironmentName + AWS_REGION_NAME: !Ref AWSRegionName + DYNAMO_TABLE: !Ref DynamoDBTable + MISTRAL_API_KEY: !Ref MistralApiKey + TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken + TELEGRAM_API_URL: !Ref TelegramApiBotUrl + WEBHOOK_URL: !Ref ApiWehookUrl Events: Api: Type: HttpApi - Properties: - ApiId: !Ref Api + # Properties: + # ApiId: !Ref Api Api: Type: AWS::Serverless::HttpApi diff --git a/src/config.py b/src/config.py index 95b64a1..99941cc 100644 --- a/src/config.py +++ b/src/config.py @@ -7,7 +7,6 @@ class Settings(BaseSettings): ENV_NAME: str = "local" AWS_REGION_NAME: str = "" DYNAMO_TABLE: str = "" - AWS_PROFILE: str = "" MISTRAL_API_KEY: str = "" TELEGRAM_BOT_TOKEN: str = "" TELEGRAM_API_URL: str = "" From aa0c990f1b9c5777a03e91ef9fc9210c3061a29e Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 16:19:29 +0000 Subject: [PATCH 12/88] Add AWS_PROFILE variable to Settings class for enhanced configuration --- src/config.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.py b/src/config.py index 99941cc..95b64a1 100644 --- a/src/config.py +++ b/src/config.py @@ -7,6 +7,7 @@ class Settings(BaseSettings): ENV_NAME: str = "local" AWS_REGION_NAME: str = "" DYNAMO_TABLE: str = "" + AWS_PROFILE: str = "" MISTRAL_API_KEY: str = "" TELEGRAM_BOT_TOKEN: str = "" TELEGRAM_API_URL: str = "" From 6c2a3611961e8bf67015f6171f51d9269a7a4c5e Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 16:36:28 +0000 Subject: [PATCH 13/88] Add DynamoDB CRUD policies to Lambda function for enhanced permissions --- infrastructure/template.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 7d77780..26266b1 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -104,6 +104,24 @@ Resources: TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken TELEGRAM_API_URL: !Ref TelegramApiBotUrl WEBHOOK_URL: !Ref ApiWehookUrl + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref DynamoDBTable + - Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:DeleteItem + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:UpdateItem + - dynamodb:BatchWriteItem + - dynamodb:BatchGetItem + - dynamodb:DescribeTable + - dynamodb:ConditionCheckItem + Resource: + - !GetAtt DynamoDBTable.Arn + - !Sub "${DynamoDBTable.Arn}/index/*" Events: Api: Type: HttpApi From ede488e57d1c3a8373d53f56125b0c918e27c033 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 16:46:17 +0000 Subject: [PATCH 14/88] Update ApiWebhookUrl parameter to hide default value and fix Api event properties --- infrastructure/template.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 26266b1..5d8374b 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -38,7 +38,7 @@ Parameters: NoEcho: true ApiWehookUrl: Type: String - Default: !Sub "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" + NoEcho: true TelegramApiBotUrl: Type: String Default: https://api.telegram.org/bot @@ -125,8 +125,8 @@ Resources: Events: Api: Type: HttpApi - # Properties: - # ApiId: !Ref Api + Properties: + ApiId: !Ref Api Api: Type: AWS::Serverless::HttpApi From ac27922c0579d40625a7f00ac6400ef9789ce2dc Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 17:08:43 +0000 Subject: [PATCH 15/88] Comment out NoEcho for sensitive parameters in template.yaml for improved security handling --- infrastructure/template.yaml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 5d8374b..56f27b3 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -23,22 +23,22 @@ Parameters: DynamoTable: Description: Name of the DynamoDB table used to persist chatbot conversations Type: String - NoEcho: true - # AwsProfile: - # Description: AWS CLI profile name used for deployment (for local/devops usage) - # Type: String - # Default: bradlab_profile + # NoEcho: true + AwsProfile: + Description: AWS CLI profile name used for deployment (for local/devops usage) + Type: String + Default: bradlab_profile MistralApiKey: Description: API key for accessing the Mistral AI service Type: String - NoEcho: true + # NoEcho: true TelegramBotToken: Description: Telegram bot token for authenticating with the Telegram API Type: String - NoEcho: true + # NoEcho: true ApiWehookUrl: Type: String - NoEcho: true + # NoEcho: true TelegramApiBotUrl: Type: String Default: https://api.telegram.org/bot From 0f40c9d2cd3b205e78c9b174d8736b528a9c215d Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 22:44:09 +0000 Subject: [PATCH 16/88] Enable NoEcho for sensitive parameters in template.yaml and implement rate limiting in main.py for enhanced security and performance --- infrastructure/template.yaml | 10 +++++----- src/main.py | 16 +++++++++++++++- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 56f27b3..f977d15 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -23,22 +23,22 @@ Parameters: DynamoTable: Description: Name of the DynamoDB table used to persist chatbot conversations Type: String - # NoEcho: true + NoEcho: true AwsProfile: Description: AWS CLI profile name used for deployment (for local/devops usage) Type: String - Default: bradlab_profile + Default: esgis_profile MistralApiKey: Description: API key for accessing the Mistral AI service Type: String - # NoEcho: true + NoEcho: true TelegramBotToken: Description: Telegram bot token for authenticating with the Telegram API Type: String - # NoEcho: true + NoEcho: true ApiWehookUrl: Type: String - # NoEcho: true + NoEcho: true TelegramApiBotUrl: Type: String Default: https://api.telegram.org/bot diff --git a/src/main.py b/src/main.py index 2b6c534..d3c1494 100644 --- a/src/main.py +++ b/src/main.py @@ -2,6 +2,10 @@ from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from mangum import Mangum +from slowapi import Limiter, _rate_limit_exceeded_handler +from slowapi.util import get_remote_address +from slowapi.errors import RateLimitExceeded +from slowapi.middleware import SlowAPIMiddleware import datetime from mistralai import Mistral import asyncio @@ -27,7 +31,6 @@ client = Mistral(api_key=api_key) - @asynccontextmanager async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") @@ -55,6 +58,17 @@ async def app_lifespan(application: FastAPI): allow_headers=["*"], ) +# Initialise le rate limiter (par IP) +limiter = Limiter(key_func=get_remote_address) +app.state.limiter = limiter +app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) +app.add_middleware(SlowAPIMiddleware) + +@app.middleware("http") +@limiter.limit("100/minute") +async def global_rate_limit(request: Request, call_next): + return await call_next(request) + @app.get("/") async def root(): From 69332f4f19ca49387fe534339eb3842ff7e7c9f9 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 23:38:00 +0000 Subject: [PATCH 17/88] Update ApiWebhookUrl parameter to use a default value and add WebhookUrl output; modify requirements to include slowapi --- infrastructure/template.yaml | 28 +++++++--------------------- requirements.txt | 1 + 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index f977d15..c7631d5 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -38,7 +38,7 @@ Parameters: NoEcho: true ApiWehookUrl: Type: String - NoEcho: true + Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook TelegramApiBotUrl: Type: String Default: https://api.telegram.org/bot @@ -103,25 +103,7 @@ Resources: MISTRAL_API_KEY: !Ref MistralApiKey TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken TELEGRAM_API_URL: !Ref TelegramApiBotUrl - WEBHOOK_URL: !Ref ApiWehookUrl - Policies: - - DynamoDBCrudPolicy: - TableName: !Ref DynamoDBTable - - Statement: - - Effect: Allow - Action: - - dynamodb:GetItem - - dynamodb:DeleteItem - - dynamodb:PutItem - - dynamodb:Query - - dynamodb:UpdateItem - - dynamodb:BatchWriteItem - - dynamodb:BatchGetItem - - dynamodb:DescribeTable - - dynamodb:ConditionCheckItem - Resource: - - !GetAtt DynamoDBTable.Arn - - !Sub "${DynamoDBTable.Arn}/index/*" + WEBHOOK_URL: !Ref ApiWehookUrl # Je veux qu'il soit construit à partir de l'url de l'api après déploiement. Format : https://api.domain.com/webhook Events: Api: Type: HttpApi @@ -140,4 +122,8 @@ Outputs: ApiUrl: Description: URL of your API Value: - Fn::Sub: 'https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/' \ No newline at end of file + Fn::Sub: 'https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/' + WebhookUrl: + Description: Full Telegram webhook URL + Value: + Fn::Sub: "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 7780859..990e6c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ mangum httpx mistralai python-telegram-bot +slowapi From c7213f4b4ffd822b6f1f4e94c600ca63c17de671 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 1 Jun 2025 23:58:48 +0000 Subject: [PATCH 18/88] Add default values for DynamoTable, MistralApiKey, and TelegramBotToken parameters in template.yaml --- infrastructure/template.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index c7631d5..1382020 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -23,6 +23,7 @@ Parameters: DynamoTable: Description: Name of the DynamoDB table used to persist chatbot conversations Type: String + Default: "" NoEcho: true AwsProfile: Description: AWS CLI profile name used for deployment (for local/devops usage) @@ -31,10 +32,12 @@ Parameters: MistralApiKey: Description: API key for accessing the Mistral AI service Type: String + Default: "" NoEcho: true TelegramBotToken: Description: Telegram bot token for authenticating with the Telegram API Type: String + Default: "" NoEcho: true ApiWehookUrl: Type: String From 6e2c6304037225e62c2f871562406d42bce1418a Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 00:57:08 +0000 Subject: [PATCH 19/88] Refactor environment variables in .example.env and template.yaml for clarity and security --- .example.env | 2 +- infrastructure/template.yaml | 76 ++++++++++++++++++------------------ 2 files changed, 39 insertions(+), 39 deletions(-) diff --git a/.example.env b/.example.env index 34c3833..970cf53 100644 --- a/.example.env +++ b/.example.env @@ -7,5 +7,5 @@ MISTRAL_API_KEY=votre-clé-mistral # TELEGRAM BOT TELEGRAM_BOT_TOKEN=votre-token-bot-telegram TELEGRAM_CHAT_ID=votre-id-chat-telegram -TELEGRAM_API_URL=https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN} +TELEGRAM_API_URL=https://api.telegram.org/bot WEBHOOK_URL=https://6cc5-102-64-223-197.ngrok-free.app/webhook \ No newline at end of file diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 1382020..f347cf2 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -16,35 +16,35 @@ Parameters: Default: bradlab ############################################################################### - AWSRegionName: - Description: AWS region where resources will be deployed (e.g. eu-west-3) - Type: String - Default: eu-west-3 - DynamoTable: - Description: Name of the DynamoDB table used to persist chatbot conversations - Type: String - Default: "" - NoEcho: true - AwsProfile: - Description: AWS CLI profile name used for deployment (for local/devops usage) - Type: String - Default: esgis_profile - MistralApiKey: - Description: API key for accessing the Mistral AI service - Type: String - Default: "" - NoEcho: true - TelegramBotToken: - Description: Telegram bot token for authenticating with the Telegram API - Type: String - Default: "" - NoEcho: true - ApiWehookUrl: - Type: String - Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook - TelegramApiBotUrl: - Type: String - Default: https://api.telegram.org/bot + # AWSRegionName: + # Description: AWS region where resources will be deployed (e.g. eu-west-3) + # Type: String + # Default: eu-west-3 + # DynamoTable: + # Description: Name of the DynamoDB table used to persist chatbot conversations + # Type: String + # Default: "" + # NoEcho: true + # AwsProfile: + # Description: AWS CLI profile name used for deployment (for local/devops usage) + # Type: String + # Default: esgis_profile + # MistralApiKey: + # Description: API key for accessing the Mistral AI service + # Type: String + # Default: "" + # NoEcho: true + # TelegramBotToken: + # Description: Telegram bot token for authenticating with the Telegram API + # Type: String + # Default: "" + # NoEcho: true + # ApiWehookUrl: + # Type: String + # Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook + # TelegramApiBotUrl: + # Type: String + # Default: https://api.telegram.org/bot ############################################################################### Resources: ############################################################################### @@ -98,15 +98,15 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - Environment: - Variables: - ENV_NAME: !Ref EnvironmentName - AWS_REGION_NAME: !Ref AWSRegionName - DYNAMO_TABLE: !Ref DynamoDBTable - MISTRAL_API_KEY: !Ref MistralApiKey - TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken - TELEGRAM_API_URL: !Ref TelegramApiBotUrl - WEBHOOK_URL: !Ref ApiWehookUrl # Je veux qu'il soit construit à partir de l'url de l'api après déploiement. Format : https://api.domain.com/webhook + # Environment: + # Variables: + # ENV_NAME: !Ref EnvironmentName + # AWS_REGION_NAME: !Ref AWSRegionName + # DYNAMO_TABLE: !Ref DynamoDBTable + # MISTRAL_API_KEY: !Ref MistralApiKey + # TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken + # TELEGRAM_API_URL: !Ref TelegramApiBotUrl + # WEBHOOK_URL: !Ref ApiWehookUrl # Je veux qu'il soit construit à partir de l'url de l'api après déploiement. Format : https://api.domain.com/webhook Events: Api: Type: HttpApi From 6b801ef3d3699740f059db0b5635a8ff348b2714 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 01:51:51 +0000 Subject: [PATCH 20/88] Remove AWS_PROFILE from config and update environment files; add Telegram API variables for improved configuration management --- .env | 10 ++++++++++ .example.env | 1 - .gitignore | 2 +- Makefile | 1 - README.md | 3 +++ src/config.py | 2 +- src/utils.py | 2 +- 7 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 .env diff --git a/.env b/.env new file mode 100644 index 0000000..7c3aafa --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +ENV_NAME=local +AWS_REGION_NAME=eu-west-3 +DYNAMO_TABLE=chatbot-dbtable-bradlab +# AWS_PROFILE=esgis_profile +MISTRAL_API_KEY=w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ + +# TELEGRAM BOT +TELEGRAM_BOT_TOKEN=7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c +TELEGRAM_API_URL=https://api.telegram.org/bot +WEBHOOK_URL=https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook \ No newline at end of file diff --git a/.example.env b/.example.env index 970cf53..7417c57 100644 --- a/.example.env +++ b/.example.env @@ -6,6 +6,5 @@ MISTRAL_API_KEY=votre-clé-mistral # TELEGRAM BOT TELEGRAM_BOT_TOKEN=votre-token-bot-telegram -TELEGRAM_CHAT_ID=votre-id-chat-telegram TELEGRAM_API_URL=https://api.telegram.org/bot WEBHOOK_URL=https://6cc5-102-64-223-197.ngrok-free.app/webhook \ No newline at end of file diff --git a/.gitignore b/.gitignore index c8d023b..379e05c 100644 --- a/.gitignore +++ b/.gitignore @@ -138,7 +138,7 @@ celerybeat.pid *.sage.py # Environments -.env +# .env .prod.env .venv env/ diff --git a/Makefile b/Makefile index cea7c38..65a96a5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,6 @@ # by default, we settle down in this region AWS_REGION ?= eu-west-3 -AWS_PROFILE ?= "esgis_profile" clean: rm -rf venv diff --git a/README.md b/README.md index 1020c41..397e00b 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,9 @@ Une API de chatbot développée avec FastAPI, conçue pour être déployée sur DYNAMO_TABLE=votre-table-dynamo AWS_PROFILE=votre-profil-aws MISTRAL_API_KEY=votre-clé-mistral + TELEGRAM_BOT_TOKEN=votre-token-bot-telegram + TELEGRAM_API_URL=https://api.telegram.org/bot + WEBHOOK_URL=votre-api-url/webhook ``` ## ▶️ Exécution diff --git a/src/config.py b/src/config.py index 95b64a1..e97b895 100644 --- a/src/config.py +++ b/src/config.py @@ -7,7 +7,7 @@ class Settings(BaseSettings): ENV_NAME: str = "local" AWS_REGION_NAME: str = "" DYNAMO_TABLE: str = "" - AWS_PROFILE: str = "" + # AWS_PROFILE: str = "" MISTRAL_API_KEY: str = "" TELEGRAM_BOT_TOKEN: str = "" TELEGRAM_API_URL: str = "" diff --git a/src/utils.py b/src/utils.py index 105e40b..1aca2e3 100644 --- a/src/utils.py +++ b/src/utils.py @@ -60,7 +60,7 @@ def get_logger(): @staticmethod def get_session(): return boto3.Session( - region_name=env_vars.AWS_REGION_NAME, profile_name=env_vars.AWS_PROFILE + region_name=env_vars.AWS_REGION_NAME, env_name=env_vars.ENV_NAME ) @staticmethod From 90abc479dd137330ae4040e2a846d50d8f648364 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 02:10:05 +0000 Subject: [PATCH 21/88] Update .gitignore to include .env file for environment variable management --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 379e05c..c8d023b 100644 --- a/.gitignore +++ b/.gitignore @@ -138,7 +138,7 @@ celerybeat.pid *.sage.py # Environments -# .env +.env .prod.env .venv env/ From b725401439ade566f701affb94c07a1a4008f563 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 02:39:18 +0000 Subject: [PATCH 22/88] Remove .env file and update template.yaml to define parameters for AWS region, DynamoDB table, Mistral API key, and Telegram bot token with appropriate descriptions and defaults. --- .env | 10 ----- infrastructure/template.yaml | 76 ++++++++++++++++++------------------ 2 files changed, 38 insertions(+), 48 deletions(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 7c3aafa..0000000 --- a/.env +++ /dev/null @@ -1,10 +0,0 @@ -ENV_NAME=local -AWS_REGION_NAME=eu-west-3 -DYNAMO_TABLE=chatbot-dbtable-bradlab -# AWS_PROFILE=esgis_profile -MISTRAL_API_KEY=w5pOu152w1ydl9m22ZGR9gnLAskQ4FwQ - -# TELEGRAM BOT -TELEGRAM_BOT_TOKEN=7916941219:AAGUKhpi9FHA-Iv-6PeGui9P_Bbq1uSC37c -TELEGRAM_API_URL=https://api.telegram.org/bot -WEBHOOK_URL=https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook \ No newline at end of file diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index f347cf2..2330bdb 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -16,35 +16,35 @@ Parameters: Default: bradlab ############################################################################### - # AWSRegionName: - # Description: AWS region where resources will be deployed (e.g. eu-west-3) - # Type: String - # Default: eu-west-3 - # DynamoTable: - # Description: Name of the DynamoDB table used to persist chatbot conversations - # Type: String - # Default: "" - # NoEcho: true - # AwsProfile: - # Description: AWS CLI profile name used for deployment (for local/devops usage) - # Type: String - # Default: esgis_profile - # MistralApiKey: - # Description: API key for accessing the Mistral AI service - # Type: String - # Default: "" - # NoEcho: true - # TelegramBotToken: - # Description: Telegram bot token for authenticating with the Telegram API - # Type: String - # Default: "" - # NoEcho: true - # ApiWehookUrl: - # Type: String - # Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook - # TelegramApiBotUrl: - # Type: String - # Default: https://api.telegram.org/bot + AWSRegionName: + Description: AWS region where resources will be deployed (e.g. eu-west-3) + Type: String + Default: eu-west-3 + DynamoTable: + Description: Name of the DynamoDB table used to persist chatbot conversations + Type: String + Default: "" + NoEcho: true + AwsProfile: + Description: AWS CLI profile name used for deployment (for local/devops usage) + Type: String + Default: esgis_profile + MistralApiKey: + Description: API key for accessing the Mistral AI service + Type: String + Default: "" + NoEcho: true + TelegramBotToken: + Description: Telegram bot token for authenticating with the Telegram API + Type: String + Default: "" + NoEcho: true + ApiWehookUrl: + Type: String + Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook + TelegramApiBotUrl: + Type: String + Default: https://api.telegram.org/bot ############################################################################### Resources: ############################################################################### @@ -98,15 +98,15 @@ Resources: CodeUri: ../ Handler: src/main.handler Runtime: python3.12 - # Environment: - # Variables: - # ENV_NAME: !Ref EnvironmentName - # AWS_REGION_NAME: !Ref AWSRegionName - # DYNAMO_TABLE: !Ref DynamoDBTable - # MISTRAL_API_KEY: !Ref MistralApiKey - # TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken - # TELEGRAM_API_URL: !Ref TelegramApiBotUrl - # WEBHOOK_URL: !Ref ApiWehookUrl # Je veux qu'il soit construit à partir de l'url de l'api après déploiement. Format : https://api.domain.com/webhook + Environment: + Variables: + ENV_NAME: !Ref EnvironmentName + AWS_REGION_NAME: !Ref AWSRegionName + DYNAMO_TABLE: !Ref DynamoDBTable + MISTRAL_API_KEY: !Ref MistralApiKey + TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken + TELEGRAM_API_URL: !Ref TelegramApiBotUrl + WEBHOOK_URL: !Ref ApiWehookUrl Events: Api: Type: HttpApi From cddc6c5ac88fd5e9b0b29eb5be0789939482507f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 12:43:01 +0000 Subject: [PATCH 23/88] Add DynamoDB policies and improve error handling in Telegram bot --- infrastructure/template.yaml | 24 +++++++++++++++++++----- src/telegram_handler.py | 33 ++++++++++++++++++--------------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 2330bdb..37b476d 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -107,6 +107,24 @@ Resources: TELEGRAM_BOT_TOKEN: !Ref TelegramBotToken TELEGRAM_API_URL: !Ref TelegramApiBotUrl WEBHOOK_URL: !Ref ApiWehookUrl + Policies: + - DynamoDBCrudPolicy: + TableName: !Ref DynamoDBTable + - Statement: + - Effect: Allow + Action: + - dynamodb:GetItem + - dynamodb:DeleteItem + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:UpdateItem + - dynamodb:BatchWriteItem + - dynamodb:BatchGetItem + - dynamodb:DescribeTable + - dynamodb:ConditionCheckItem + Resource: + - !GetAtt DynamoDBTable.Arn + - !Sub "${DynamoDBTable.Arn}/index/*" Events: Api: Type: HttpApi @@ -123,10 +141,6 @@ Outputs: DynamoDBTableName: Value: !Ref DynamoDBTable ApiUrl: - Description: URL of your API + Description: URL of our API Value: Fn::Sub: 'https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/' - WebhookUrl: - Description: Full Telegram webhook URL - Value: - Fn::Sub: "https://${Api}.execute-api.${AWS::Region}.${AWS::URLSuffix}/webhook" \ No newline at end of file diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 00e1144..dce9a16 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,6 +1,7 @@ from telegram import Update, Bot from telegram.ext import Application, MessageHandler, filters, CommandHandler from mistralai import Mistral +import asyncio from .dynamodb_repository import dynamodb_repo from .config import env_vars @@ -34,13 +35,12 @@ async def start_command(update: Update, context): user_name = user.full_name or user.username or "N/A" await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" - await update.message.reply_text() + # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") async def handle_message(update: Update, context): - # Traite tous les messages texte et utilise MistralAI pour répondre. try: if update.message and update.message.text: user = update.message.from_user @@ -49,23 +49,24 @@ async def handle_message(update: Update, context): message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - # Enregistrer le message utilisateur via le repository await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") try: - chat_response = mistral_client.chat.complete( - model=MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] + # Timeout de 5 secondes sur l'appel à MistralAI + chat_response = await asyncio.wait_for( + mistral_client.chat.complete( + model=MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ), + timeout=60 # secondes ) - response_text = chat_response.choices[0].message.content bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # Enregistrer la reponse du bot await dynamodb_repo.save_message( chat_id, bot_message.message_id, @@ -75,15 +76,17 @@ async def handle_message(update: Update, context): "bot", MISTRAL_MODEL ) + except asyncio.TimeoutError: + error_response = "⏱️ Le service met trop de temps à répondre, veuillez réessayer plus tard." + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") except Exception as e: Utils.log_info(f"Erreur lors de l'interaction avec MistralAI ou Telegram: {e}") error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." - # Enregistrer le message d'erreur du bot via le dépôt bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") except Exception as e: Utils.log_error("Traitement du message échoué.") - async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot From fc3901433e7cb4630f70daa373fc7258319ce046 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 13:15:53 +0000 Subject: [PATCH 24/88] Improve Telegram webhook configuration to avoid unnecessary updates --- src/telegram_handler.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index dce9a16..602a60b 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -98,16 +98,18 @@ async def setup_ptb_handlers(): Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") async def configure_telegram_webhook(): - # Configure le webhook Telegram avec l'URL définie. if not API_WEBHOOK_URL: Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") return bot = Bot(TELEGRAM_BOT_TOKEN) try: - await bot.set_webhook(url=API_WEBHOOK_URL) - Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") - Utils.log_info(f"Webhook Telegram API : {API_WEBHOOK_URL}") + current_webhook = await bot.get_webhook_info() + if current_webhook.url != API_WEBHOOK_URL: + await bot.set_webhook(url=API_WEBHOOK_URL) + Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") + else: + Utils.log_info("Webhook déjà configuré, aucune modification nécessaire.") except Exception as e: Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {e}") From a2dc8f0530099349c6aa7b584095f6c29803fc5c Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 14:10:09 +0000 Subject: [PATCH 25/88] Add UUID generation for message IDs and include 'id' attribute in DynamoDB item --- infrastructure/template.yaml | 1 + src/dynamodb_repository.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 37b476d..99156f7 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -82,6 +82,7 @@ Resources: Projection: ProjectionType: "INCLUDE" NonKeyAttributes: # Attributs à projeter en plus des clés de l'index et de la table + - "id" - "chat_id" - "message_id" - "role" diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index e167c65..b992674 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -1,6 +1,5 @@ import boto3 -import datetime -import asyncio +import datetime, asyncio, uuid from botocore.exceptions import ClientError from boto3.dynamodb.conditions import Key, Attr @@ -44,8 +43,9 @@ async def save_message( Sauvegarde un message (utilisateur ou bot) dans la table DynamoDB. """ try: - + id = str(uuid.uuid4()) # Génère un UUID item = { + 'id': id, 'chat_id': str(chat_id), 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'message_id': str(message_id), From 623019a6260ad9b85ef873b6877db4385b5638ab Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 14:32:21 +0000 Subject: [PATCH 26/88] Remove 'id' attribute from DynamoDB index projection in template.yaml --- infrastructure/template.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 99156f7..37b476d 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -82,7 +82,6 @@ Resources: Projection: ProjectionType: "INCLUDE" NonKeyAttributes: # Attributs à projeter en plus des clés de l'index et de la table - - "id" - "chat_id" - "message_id" - "role" From 9052c059a380ac1e62c55b9baa883a904f43d581 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 15:52:43 +0000 Subject: [PATCH 27/88] Refactor logging methods in Utils class to use a centralized logger instance --- src/utils.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/utils.py b/src/utils.py index 1aca2e3..440ad28 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,4 @@ -from datetime import datetime import json -from uuid import uuid4 import logging from typing import List @@ -9,6 +7,11 @@ from src.config import env_vars ## Simple edit +logging.basicConfig( + level=logging.INFO, + format="%(asctime)s %(levelname)s %(name)s %(message)s" +) +logger = logging.getLogger("chatbot-bradlab-logs") class Utils: @@ -19,43 +22,43 @@ def log_info(message): """_summary_ Log a simple info message """ - logging.getLogger("uvicorn.error").info(msg=f"==> {message}") + logger.info(msg=f"==> {message}") @staticmethod def log_warning(message): """_summary_ Log a simple warning message """ - logging.getLogger("uvicorn.error").warning(msg=f"==> {message}") + logger.warning(msg=f"==> {message}") @staticmethod def log_debug(message): """_summary_ Log a debug message """ - logging.getLogger("uvicorn.error").debug(msg=f"==> {message}") + logger.debug(msg=f"==> {message}") @staticmethod def log_error(message): """_summary_ Log an error message """ - logging.getLogger("uvicorn.error").error(msg=f"==> {message}") + logger.error(msg=f"==> {message}") @staticmethod def log_list(elements: List[any]): if elements: - logging.getLogger("uvicorn.error").info( + logger.info( msg=f"Displaying all the {len(elements)} elements of the list" ) for i in range(len(elements)): - logging.getLogger("uvicorn.error").info( + logger.info( msg=f"##### {i} ==> {json.dumps(elements[i], indent=4)}" ) @staticmethod def get_logger(): - return logging.getLogger("uvicorn.error") + return logger @staticmethod def get_session(): From 2b50d4a11ccb1eba072d34d4fadc8a93bc2ed9ea Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 16:17:33 +0000 Subject: [PATCH 28/88] Refactor chat response handling to remove asyncio timeout and simplify MistralAI call --- src/telegram_handler.py | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 602a60b..5571744 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -53,17 +53,26 @@ async def handle_message(update: Update, context): try: # Timeout de 5 secondes sur l'appel à MistralAI - chat_response = await asyncio.wait_for( - mistral_client.chat.complete( - model=MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] - ), - timeout=60 # secondes + # chat_response = await asyncio.wait_for( + # mistral_client.chat.complete( + # model=MISTRAL_MODEL, + # messages=[ + # { + # "role": "user", + # "content": user_message, + # }, + # ] + # ), + # timeout=60 # secondes + # ) + chat_response = mistral_client.chat.complete( + model=MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] ) response_text = chat_response.choices[0].message.content bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) @@ -76,10 +85,10 @@ async def handle_message(update: Update, context): "bot", MISTRAL_MODEL ) - except asyncio.TimeoutError: - error_response = "⏱️ Le service met trop de temps à répondre, veuillez réessayer plus tard." - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + # except asyncio.TimeoutError: + # error_response = "⏱️ Le service met trop de temps à répondre, veuillez réessayer plus tard." + # bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") except Exception as e: Utils.log_info(f"Erreur lors de l'interaction avec MistralAI ou Telegram: {e}") error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." From 63e5cf9b008a22824703abdf04ffc859faa0168c Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 17:25:25 +0000 Subject: [PATCH 29/88] Refactor logging configuration in Utils class and enhance error handling in Telegram command processing --- src/main.py | 2 +- src/telegram_handler.py | 30 +++++++++++++++++++----------- src/utils.py | 5 +---- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/main.py b/src/main.py index d3c1494..fa1f5c1 100644 --- a/src/main.py +++ b/src/main.py @@ -35,7 +35,7 @@ async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") await setup_ptb_handlers() - asyncio.create_task(configure_telegram_webhook()) # <-- C'est la source probable du problème + asyncio.create_task(configure_telegram_webhook()) yield # L'application est maintenant prête à recevoir des requêtes diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 5571744..e6245e4 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -27,17 +27,22 @@ ptb_app = Application.builder().token(TELEGRAM_BOT_TOKEN).updater(None).build() async def start_command(update: Update, context): - # Gère la commande /start. - user = update.message.from_user - user_message = update.message.text - chat_id = update.message.chat_id - message_id = update.message.message_id - user_name = user.full_name or user.username or "N/A" - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) - response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" - # await update.message.reply_text() - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + try: + # Gère la commande /start. + user = update.message.from_user + user_message = update.message.text + chat_id = update.message.chat_id + message_id = update.message.message_id + user_name = user.full_name or user.username or "N/A" + Utils.log_error(f"===== Start command Initializing ====== ") + + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" + # await update.message.reply_text() + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + except Exception as e: + Utils.log_error(f"===== Start command error ====== $e") async def handle_message(update: Update, context): @@ -99,7 +104,10 @@ async def handle_message(update: Update, context): async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot + Utils.log_warning("==== Configuration du webhook ====") + ptb_app.add_handler(CommandHandler("start", start_command)) + Utils.log_warning("==== Handle first message ====") ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) await ptb_app.initialize() Utils.log_info("Handlers Telegram initialisés.") diff --git a/src/utils.py b/src/utils.py index 440ad28..ba76307 100644 --- a/src/utils.py +++ b/src/utils.py @@ -7,10 +7,7 @@ from src.config import env_vars ## Simple edit -logging.basicConfig( - level=logging.INFO, - format="%(asctime)s %(levelname)s %(name)s %(message)s" -) +logging.basicConfig() logger = logging.getLogger("chatbot-bradlab-logs") class Utils: From a3ab66172369b81a7d4ba1bdaa5bc34330b8e252 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 17:31:39 +0000 Subject: [PATCH 30/88] Fix error logging in start_command and add webhook connection log in configure_telegram_webhook --- src/telegram_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index e6245e4..2d0313d 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -42,7 +42,7 @@ async def start_command(update: Update, context): bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") except Exception as e: - Utils.log_error(f"===== Start command error ====== $e") + Utils.log_error(f"===== Start command error ====== {e}") async def handle_message(update: Update, context): @@ -122,6 +122,7 @@ async def configure_telegram_webhook(): bot = Bot(TELEGRAM_BOT_TOKEN) try: current_webhook = await bot.get_webhook_info() + Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {env_vars.WEBHOOK_URL}") if current_webhook.url != API_WEBHOOK_URL: await bot.set_webhook(url=API_WEBHOOK_URL) Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") From f2e891993c6cc5e593977701a2c2a3d34cba7787 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Mon, 2 Jun 2025 23:14:24 +0000 Subject: [PATCH 31/88] Enhance Jenkinsfile to use credentials for Telegram bot and Mistral API keys, and add webhook configuration stage with error handling in webhook setup script. --- Jenkinsfile | 49 +++++++++++++++++++++++++++-- requirements.txt | 1 + seed/webhook.py | 69 +++++++++++++++++++++++++++++++++++++++++ src/main.py | 2 +- src/telegram_handler.py | 4 +-- 5 files changed, 120 insertions(+), 5 deletions(-) create mode 100644 seed/webhook.py diff --git a/Jenkinsfile b/Jenkinsfile index bde5622..4c06656 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -8,7 +8,8 @@ pipeline { environment { // Define environment variables here BOT_NAME = 'awesome-bot' - // BOT_TOKEN = credentials('telegram-bot-token') + TELEGRAM_BOT_TOKEN = credentials('telegram-bot-token') + MISTRAL_API_KEY = credentials('mistral-api-key') } stages { @@ -65,7 +66,51 @@ pipeline { script { // Add your deployment commands here echo "Deploying the project..." - sh "make deploy env=${BRANCH_NAME}" + withCredentials([ + string(credentialsId: 'telegram-bot-token', variable: 'TELEGRAM_BOT_TOKEN'), + string(credentialsId: 'mistral-api-key', variable: 'MISTRAL_API_KEY') + ]) { + sh """ + make deploy env=${BRANCH_NAME} \ + TELEGRAM_BOT_TOKEN=${TELEGRAM_BOT_TOKEN} \ + MISTRAL_API_KEY=${MISTRAL_API_KEY} + """ + } + } + } + } + + stage('Configure Webhook') { + when { + anyOf { + branch 'alwil17' + branch 'dev' + branch 'preprod' + } + } + steps { + script { + // Get the API URL from CloudFormation outputs + def apiUrl = sh( + script: """ + aws cloudformation describe-stacks \ + --stack-name multi-stack-${BRANCH_NAME} \ + --region eu-west-3 \ + --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \ + --output text + """, + returnStdout: true + ).trim() + + // Configure the webhook + withCredentials([string(credentialsId: 'telegram-bot-token', variable: 'TELEGRAM_BOT_TOKEN')]) { + sh """ + # Activer l'environnement virtuel et exécuter le script + . .venv/bin/activate + python seed/webhook.py --url "${apiUrl}/webhook" + deactivate + """ + } } } } diff --git a/requirements.txt b/requirements.txt index 990e6c9..20ecf3e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ httpx mistralai python-telegram-bot slowapi +requests diff --git a/seed/webhook.py b/seed/webhook.py new file mode 100644 index 0000000..3092e41 --- /dev/null +++ b/seed/webhook.py @@ -0,0 +1,69 @@ +import os +import sys +import argparse +import requests +from dotenv import load_dotenv + +# Charger les variables d'environnement +load_dotenv() + +def setup_webhook(webhook_url): + """ + Configure le webhook Telegram pour le bot. + + Args: + webhook_url (str): L'URL complète du webhook (https://...) + """ + # Récupérer le token depuis les variables d'environnement + bot_token = os.getenv('TELEGRAM_BOT_TOKEN') + if not bot_token: + sys.exit(1) + + if not webhook_url.startswith('https://'): + sys.exit(1) + + # URL de l'API Telegram pour configurer le webhook + api_url = f"https://api.telegram.org/bot{bot_token}/setWebhook" + + try: + # Vérifier d'abord le webhook actuel + info_response = requests.get( + f"https://api.telegram.org/bot{bot_token}/getWebhookInfo" + ) + info_response.raise_for_status() + current_webhook = info_response.json() + + if 'result' in current_webhook and 'url' in current_webhook['result']: + current_url = current_webhook['result']['url'] + if current_url == webhook_url: + return + else: + print(f"Configuration initiale du webhook sur {webhook_url}") + + # Configurer le nouveau webhook + response = requests.post( + api_url, + json={'url': webhook_url} + ) + response.raise_for_status() + + result = response.json() + if result.get('ok'): + print("Webhook configuré avec succès!") + else: + print(f"Erreur: {result.get('description', 'Erreur inconnue')}") + sys.exit(1) + + except requests.exceptions.RequestException as e: + print(f"Erreur lors de la configuration du webhook: {str(e)}") + sys.exit(1) + +def main(): + parser = argparse.ArgumentParser(description='Configure le webhook Telegram pour le bot') + parser.add_argument('--url', required=True, help='URL du webhook (https://...)') + args = parser.parse_args() + + setup_webhook(args.url) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/main.py b/src/main.py index fa1f5c1..16746ce 100644 --- a/src/main.py +++ b/src/main.py @@ -35,7 +35,7 @@ async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") await setup_ptb_handlers() - asyncio.create_task(configure_telegram_webhook()) + # asyncio.create_task(configure_telegram_webhook()) yield # L'application est maintenant prête à recevoir des requêtes diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 2d0313d..f6c3042 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -36,11 +36,11 @@ async def start_command(update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_error(f"===== Start command Initializing ====== ") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") except Exception as e: Utils.log_error(f"===== Start command error ====== {e}") From d40ecb8de6f18ebdb465a0a3c265df22e6e7a804 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 00:02:54 +0000 Subject: [PATCH 32/88] Update webhook configuration in Jenkinsfile and enhance FastAPI webhook handling with authorization checks --- Jenkinsfile | 8 +++--- src/main.py | 42 ++++++++++++++++++++++++++- src/telegram_handler.py | 63 +++++++++++++++++++++++++++-------------- 3 files changed, 87 insertions(+), 26 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 4c06656..41affa3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -105,10 +105,10 @@ pipeline { // Configure the webhook withCredentials([string(credentialsId: 'telegram-bot-token', variable: 'TELEGRAM_BOT_TOKEN')]) { sh """ - # Activer l'environnement virtuel et exécuter le script - . .venv/bin/activate - python seed/webhook.py --url "${apiUrl}/webhook" - deactivate + curl -X POST "${apiUrl}/set-webhook" \\ + -H "Authorization: Bearer ${TELEGRAM_BOT_TOKEN}" \\ + -H "Content-Type: application/json" \\ + -d '{ "url": "${apiUrl}/webhook" }' """ } } diff --git a/src/main.py b/src/main.py index 16746ce..32e4cd9 100644 --- a/src/main.py +++ b/src/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, Request, HTTPException, Query +from fastapi import FastAPI, Request, HTTPException, Header, Query, status from fastapi.middleware.cors import CORSMiddleware from contextlib import asynccontextmanager from mangum import Mangum @@ -8,6 +8,10 @@ from slowapi.middleware import SlowAPIMiddleware import datetime from mistralai import Mistral +from pydantic import BaseModel, HttpUrl +from typing import Optional +from .config import env_vars + import asyncio from .dynamodb_repository import dynamodb_repo @@ -30,6 +34,9 @@ model = "mistral-small-latest" client = Mistral(api_key=api_key) +class WebhookRequest(BaseModel): + url: HttpUrl + @asynccontextmanager async def app_lifespan(application: FastAPI): @@ -105,6 +112,39 @@ async def chat(question: str): Utils.insert_data(response) return response +# @app.post("/set-webhook") +# async def set_webhook(request: Request, authorization: str = Header(None)): +# data = await request.json() +# url = data.get("url") +# # utiliser le token dans 'authorization' pour vérifier +# # puis définir le webhook ici +# if (authorization == env_vars.TELEGRAM_BOT_TOKEN): +# asyncio.create_task(configure_telegram_webhook(url)) +# return {"status": "ok", "webhook_set_to": url} +# return {"status": "error", "webhook_set_to": url} + +# Modèle pour la réponse +class WebhookResponse(BaseModel): + status: str + webhook_set_to: HttpUrl + +@app.post("/set-webhook", response_model=WebhookResponse, summary="Configure Telegram Webhook") +async def set_webhook( + payload: WebhookRequest, + authorization: Optional[str] = Header(None, description="Bearer token for authentication") +): + if authorization is None or not authorization.startswith("Bearer "): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Missing or invalid Authorization header") + + token = authorization.split("Bearer ")[-1] + + # Vérifie que le token correspond à celui attendu (à adapter selon ton besoin) + if token != env_vars.TELEGRAM_BOT_TOKEN: + raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token") + + asyncio.create_task(configure_telegram_webhook(payload.url)) + return WebhookResponse(status="ok", webhook_set_to=payload.url) + @app.post("/webhook", description="Endpoint pour recevoir les mises à jour ou changement dans le bot Telegram") async def telegram_webhook(request: Request): try: diff --git a/src/telegram_handler.py b/src/telegram_handler.py index f6c3042..3334210 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -34,7 +34,7 @@ async def start_command(update: Update, context): chat_id = update.message.chat_id message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - Utils.log_error(f"===== Start command Initializing ====== ") + Utils.log_error(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" @@ -81,26 +81,30 @@ async def handle_message(update: Update, context): ) response_text = chat_response.choices[0].message.content bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) - # except asyncio.TimeoutError: - # error_response = "⏱️ Le service met trop de temps à répondre, veuillez réessayer plus tard." - # bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + try: + # Enregistrement dans DynamoDB + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) + except Exception as db_error: + Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: Utils.log_info(f"Erreur lors de l'interaction avec MistralAI ou Telegram: {e}") error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + try: + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + except Exception as db_error: + Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: Utils.log_error("Traitement du message échoué.") + async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot @@ -114,18 +118,35 @@ async def setup_ptb_handlers(): except Exception as e: Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") -async def configure_telegram_webhook(): - if not API_WEBHOOK_URL: +# async def configure_telegram_webhook(webhook_url: str): +# if not API_WEBHOOK_URL: +# Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") +# return + +# bot = Bot(TELEGRAM_BOT_TOKEN) +# try: +# current_webhook = await bot.get_webhook_info() +# Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {env_vars.WEBHOOK_URL}") +# if current_webhook.url != API_WEBHOOK_URL: +# await bot.set_webhook(url=API_WEBHOOK_URL) +# Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") +# else: +# Utils.log_info("Webhook déjà configuré, aucune modification nécessaire.") +# except Exception as e: +# Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {e}") +async def configure_telegram_webhook(webhook_url: str): + api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" + if not api_webhook_url: Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") return bot = Bot(TELEGRAM_BOT_TOKEN) try: current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {env_vars.WEBHOOK_URL}") - if current_webhook.url != API_WEBHOOK_URL: - await bot.set_webhook(url=API_WEBHOOK_URL) - Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") + Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {webhook_url}") + if current_webhook.url != api_webhook_url: + await bot.set_webhook(url=api_webhook_url) + Utils.log_info(f"Webhook Telegram configuré sur : {webhook_url}") else: Utils.log_info("Webhook déjà configuré, aucune modification nécessaire.") except Exception as e: From 4d57d681756930f2ea34747d3090dcda4664bda9 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 00:48:53 +0000 Subject: [PATCH 33/88] Enhance .dockerignore to include .dev.env and update template.yaml to set ApiWebhookUrl default to an empty string. Refactor error handling in telegram_handler.py for database operations and webhook configuration. --- .dockerignore | 1 + infrastructure/template.yaml | 2 +- src/main.py | 15 ++---------- src/telegram_handler.py | 47 ++++++++++++++++++------------------ 4 files changed, 27 insertions(+), 38 deletions(-) diff --git a/.dockerignore b/.dockerignore index 9a11747..a99d668 100644 --- a/.dockerignore +++ b/.dockerignore @@ -30,6 +30,7 @@ dist/ # Ignorer les fichiers d'environnement locaux .env +.dev.env *.env # Ignorer les fichiers de build ou de CI/CD qui ne sont pas nécessaires à l'exécution de l'app diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 37b476d..2fca501 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -41,7 +41,7 @@ Parameters: NoEcho: true ApiWehookUrl: Type: String - Default: https://p48s0cupm7.execute-api.eu-west-3.amazonaws.com/webhook + Default: "" TelegramApiBotUrl: Type: String Default: https://api.telegram.org/bot diff --git a/src/main.py b/src/main.py index 32e4cd9..fdd2e04 100644 --- a/src/main.py +++ b/src/main.py @@ -47,7 +47,7 @@ async def app_lifespan(application: FastAPI): yield # L'application est maintenant prête à recevoir des requêtes Utils.log_info("Application KOZ API arrêtée.") - await shutdown_ptb() + # await shutdown_ptb() app = FastAPI( @@ -112,23 +112,12 @@ async def chat(question: str): Utils.insert_data(response) return response -# @app.post("/set-webhook") -# async def set_webhook(request: Request, authorization: str = Header(None)): -# data = await request.json() -# url = data.get("url") -# # utiliser le token dans 'authorization' pour vérifier -# # puis définir le webhook ici -# if (authorization == env_vars.TELEGRAM_BOT_TOKEN): -# asyncio.create_task(configure_telegram_webhook(url)) -# return {"status": "ok", "webhook_set_to": url} -# return {"status": "error", "webhook_set_to": url} - # Modèle pour la réponse class WebhookResponse(BaseModel): status: str webhook_set_to: HttpUrl -@app.post("/set-webhook", response_model=WebhookResponse, summary="Configure Telegram Webhook") +@app.post("/set-webhook", response_model=WebhookResponse, include_in_schema=False) async def set_webhook( payload: WebhookRequest, authorization: Optional[str] = Header(None, description="Bearer token for authentication") diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 3334210..39171c5 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -36,13 +36,19 @@ async def start_command(update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_error(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + try: + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + except Exception as db_error: + Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + try: + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + except Exception as db_error: + Utils.log_error(f"DB_ERROR.start_command 2 ====== {e}") except Exception as e: - Utils.log_error(f"===== Start command error ====== {e}") + Utils.log_error(f"Start command error ====== {e}") async def handle_message(update: Update, context): @@ -54,22 +60,12 @@ async def handle_message(update: Update, context): message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - try: - # Timeout de 5 secondes sur l'appel à MistralAI - # chat_response = await asyncio.wait_for( - # mistral_client.chat.complete( - # model=MISTRAL_MODEL, - # messages=[ - # { - # "role": "user", - # "content": user_message, - # }, - # ] - # ), - # timeout=60 # secondes - # ) + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + except Exception as db_error: + Utils.log_error(f"DB_ERROR.handle_message ====== {e}") + + try: chat_response = mistral_client.chat.complete( model=MISTRAL_MODEL, messages=[ @@ -108,13 +104,13 @@ async def handle_message(update: Update, context): async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot - Utils.log_warning("==== Configuration du webhook ====") + Utils.log_warning("Configuration du webhook ====") ptb_app.add_handler(CommandHandler("start", start_command)) - Utils.log_warning("==== Handle first message ====") + Utils.log_warning("Handle first message ====") ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) await ptb_app.initialize() - Utils.log_info("Handlers Telegram initialisés.") + Utils.log_warning("Handlers Telegram initialisés.") except Exception as e: Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") @@ -145,10 +141,13 @@ async def configure_telegram_webhook(webhook_url: str): current_webhook = await bot.get_webhook_info() Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {webhook_url}") if current_webhook.url != api_webhook_url: - await bot.set_webhook(url=api_webhook_url) - Utils.log_info(f"Webhook Telegram configuré sur : {webhook_url}") + try: + await bot.set_webhook(url=api_webhook_url) + Utils.log_info(f"Webhook Telegram configuré sur : {webhook_url}") + except Exception as bot_error: + Utils.log_error(f"Erreur configuration du webhook url : {e}") else: - Utils.log_info("Webhook déjà configuré, aucune modification nécessaire.") + Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") except Exception as e: Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {e}") From 12cfb6ef74c4078404ce8c04000cd721fca4de43 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 01:29:01 +0000 Subject: [PATCH 34/88] Add warning log for user messages in handle_message function --- src/telegram_handler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 39171c5..909ec33 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -61,6 +61,7 @@ async def handle_message(update: Update, context): user_name = user.full_name or user.username or "N/A" try: + Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") except Exception as db_error: Utils.log_error(f"DB_ERROR.handle_message ====== {e}") From ee10705f77165a870c1a85d0062bf28d08463bb0 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 11:26:18 +0000 Subject: [PATCH 35/88] Refactor .dockerignore and .gitignore to manage environment files, remove unused webhook script, and update requirements.txt by removing requests package. --- .dockerignore | 1 - .gitignore | 1 + requirements.txt | 1 - seed/webhook.py | 69 ----------------------------------------- src/telegram_handler.py | 19 +----------- 5 files changed, 2 insertions(+), 89 deletions(-) delete mode 100644 seed/webhook.py diff --git a/.dockerignore b/.dockerignore index a99d668..9a11747 100644 --- a/.dockerignore +++ b/.dockerignore @@ -30,7 +30,6 @@ dist/ # Ignorer les fichiers d'environnement locaux .env -.dev.env *.env # Ignorer les fichiers de build ou de CI/CD qui ne sont pas nécessaires à l'exécution de l'app diff --git a/.gitignore b/.gitignore index c8d023b..3bba28f 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ celerybeat.pid # Environments .env .prod.env +.dev.env .venv env/ venv/ diff --git a/requirements.txt b/requirements.txt index 20ecf3e..990e6c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,3 @@ httpx mistralai python-telegram-bot slowapi -requests diff --git a/seed/webhook.py b/seed/webhook.py deleted file mode 100644 index 3092e41..0000000 --- a/seed/webhook.py +++ /dev/null @@ -1,69 +0,0 @@ -import os -import sys -import argparse -import requests -from dotenv import load_dotenv - -# Charger les variables d'environnement -load_dotenv() - -def setup_webhook(webhook_url): - """ - Configure le webhook Telegram pour le bot. - - Args: - webhook_url (str): L'URL complète du webhook (https://...) - """ - # Récupérer le token depuis les variables d'environnement - bot_token = os.getenv('TELEGRAM_BOT_TOKEN') - if not bot_token: - sys.exit(1) - - if not webhook_url.startswith('https://'): - sys.exit(1) - - # URL de l'API Telegram pour configurer le webhook - api_url = f"https://api.telegram.org/bot{bot_token}/setWebhook" - - try: - # Vérifier d'abord le webhook actuel - info_response = requests.get( - f"https://api.telegram.org/bot{bot_token}/getWebhookInfo" - ) - info_response.raise_for_status() - current_webhook = info_response.json() - - if 'result' in current_webhook and 'url' in current_webhook['result']: - current_url = current_webhook['result']['url'] - if current_url == webhook_url: - return - else: - print(f"Configuration initiale du webhook sur {webhook_url}") - - # Configurer le nouveau webhook - response = requests.post( - api_url, - json={'url': webhook_url} - ) - response.raise_for_status() - - result = response.json() - if result.get('ok'): - print("Webhook configuré avec succès!") - else: - print(f"Erreur: {result.get('description', 'Erreur inconnue')}") - sys.exit(1) - - except requests.exceptions.RequestException as e: - print(f"Erreur lors de la configuration du webhook: {str(e)}") - sys.exit(1) - -def main(): - parser = argparse.ArgumentParser(description='Configure le webhook Telegram pour le bot') - parser.add_argument('--url', required=True, help='URL du webhook (https://...)') - args = parser.parse_args() - - setup_webhook(args.url) - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 909ec33..bb862c0 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -11,8 +11,7 @@ # Récupérer les jetons depuis les variables d'environnement TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY -API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={env_vars.WEBHOOK_URL}" -# API_WEBHOOK_URL = env_vars.WEBHOOK_URL +# API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={env_vars.WEBHOOK_URL}" MISTRAL_MODEL = "mistral-large-latest" if not TELEGRAM_BOT_TOKEN: @@ -115,22 +114,6 @@ async def setup_ptb_handlers(): except Exception as e: Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") -# async def configure_telegram_webhook(webhook_url: str): -# if not API_WEBHOOK_URL: -# Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") -# return - -# bot = Bot(TELEGRAM_BOT_TOKEN) -# try: -# current_webhook = await bot.get_webhook_info() -# Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {env_vars.WEBHOOK_URL}") -# if current_webhook.url != API_WEBHOOK_URL: -# await bot.set_webhook(url=API_WEBHOOK_URL) -# Utils.log_info(f"Webhook Telegram configuré sur : {env_vars.WEBHOOK_URL}") -# else: -# Utils.log_info("Webhook déjà configuré, aucune modification nécessaire.") -# except Exception as e: -# Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {e}") async def configure_telegram_webhook(webhook_url: str): api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" if not api_webhook_url: From 5472ca06077823f90a78f7435ff82ccda6f8905e Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 12:17:48 +0000 Subject: [PATCH 36/88] Refactor handle_message function for improved error logging and add global error handler for Telegram updates --- src/telegram_handler.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index bb862c0..41b85f2 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,5 +1,5 @@ from telegram import Update, Bot -from telegram.ext import Application, MessageHandler, filters, CommandHandler +from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes from mistralai import Mistral import asyncio @@ -63,10 +63,11 @@ async def handle_message(update: Update, context): Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") except Exception as db_error: - Utils.log_error(f"DB_ERROR.handle_message ====== {e}") + Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") try: - chat_response = mistral_client.chat.complete( + chat_response = await asyncio.to_thread( + mistral_client.chat.complete, model=MISTRAL_MODEL, messages=[ { @@ -100,6 +101,10 @@ async def handle_message(update: Update, context): Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: Utils.log_error("Traitement du message échoué.") + +async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: + Utils.log_error(f"== Unhandled Telegram exception: {context.error}") + async def setup_ptb_handlers(): try: @@ -109,6 +114,7 @@ async def setup_ptb_handlers(): ptb_app.add_handler(CommandHandler("start", start_command)) Utils.log_warning("Handle first message ====") ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) + ptb_app.add_error_handler(_error_handler) await ptb_app.initialize() Utils.log_warning("Handlers Telegram initialisés.") except Exception as e: From 372c157d1c2361489420c20a456dc3b23ce4e8ff Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 12:47:52 +0000 Subject: [PATCH 37/88] Comment out UUID generation in save_message method of DynamoDBRepository --- src/dynamodb_repository.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index b992674..4cfbcbd 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -43,9 +43,9 @@ async def save_message( Sauvegarde un message (utilisateur ou bot) dans la table DynamoDB. """ try: - id = str(uuid.uuid4()) # Génère un UUID + # id = str(uuid.uuid4()) # Génère un UUID item = { - 'id': id, + # 'id': id, 'chat_id': str(chat_id), 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'message_id': str(message_id), From 7ff8030ca73b93b8c471193538f160aaab8d6015 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 13:15:00 +0000 Subject: [PATCH 38/88] Uncomment UUID generation in save_message method and enhance webhook logging with current webhook URL in configure_telegram_webhook function --- src/dynamodb_repository.py | 4 ++-- src/telegram_handler.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 4cfbcbd..b992674 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -43,9 +43,9 @@ async def save_message( Sauvegarde un message (utilisateur ou bot) dans la table DynamoDB. """ try: - # id = str(uuid.uuid4()) # Génère un UUID + id = str(uuid.uuid4()) # Génère un UUID item = { - # 'id': id, + 'id': id, 'chat_id': str(chat_id), 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), 'message_id': str(message_id), diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 41b85f2..1d25cd9 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -129,7 +129,7 @@ async def configure_telegram_webhook(webhook_url: str): bot = Bot(TELEGRAM_BOT_TOKEN) try: current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {webhook_url}") + Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {webhook_url} => {current_webhook.url}") if current_webhook.url != api_webhook_url: try: await bot.set_webhook(url=api_webhook_url) From c838ce20a92a92acf61bd688534177c5b209a1f2 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 13:33:23 +0000 Subject: [PATCH 39/88] Improve webhook error logging by including the webhook URL in the error message and remove unnecessary warning log during handler setup. --- src/telegram_handler.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 1d25cd9..87abcd1 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -109,8 +109,6 @@ async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot - Utils.log_warning("Configuration du webhook ====") - ptb_app.add_handler(CommandHandler("start", start_command)) Utils.log_warning("Handle first message ====") ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) @@ -139,7 +137,7 @@ async def configure_telegram_webhook(webhook_url: str): else: Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") except Exception as e: - Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {e}") + Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") async def process_telegram_update(update_json: dict): update = Update.de_json(update_json, ptb_app.bot) From f3866a9950facf372f60febf8c23fd8bd7bd2dff Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 13:44:38 +0000 Subject: [PATCH 40/88] Refactor start_command response text for improved user engagement and change log level from error to warning for initialization message. --- src/telegram_handler.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 87abcd1..4644adb 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -33,13 +33,20 @@ async def start_command(update: Update, context): chat_id = update.message.chat_id message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - Utils.log_error(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") + Utils.log_warning(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") try: await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) except Exception as db_error: Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") - response_text = f"Bonjour {user_name} ! Je suis Koz votre bot intelligent de causerie. Posez-moi une question !" + response_text = ( + "Bonjour {user_name} ! Comment puis-je vous aider aujourd'hui ? Discutons ensemble. Voici quelques suggestions pour commencer :\n\n" + "• Vous pouvez me poser une question sur un sujet qui vous intéresse.\n" + "• Nous pouvons jouer à un jeu de mots, comme l'association d'idées ou le jeu des 20 questions.\n" + "• Vous pouvez partager quelque chose sur vous, et j'essaierai de faire le lien.\n" + "• Nous pouvons discuter d'un événement récent ou d'un sujet d'actualité.\n\n" + "Par quoi souhaitez-vous commencer ?" + ) # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) try: @@ -92,7 +99,6 @@ async def handle_message(update: Update, context): except Exception as db_error: Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: - Utils.log_info(f"Erreur lors de l'interaction avec MistralAI ou Telegram: {e}") error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) try: From 199b8ee4527731f8b416bb2bdff813b6b25e5bb5 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 14:28:18 +0000 Subject: [PATCH 41/88] Log warning message before configuring Telegram webhook and await the configuration task --- src/main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index fdd2e04..c1a5f9b 100644 --- a/src/main.py +++ b/src/main.py @@ -131,7 +131,8 @@ async def set_webhook( if token != env_vars.TELEGRAM_BOT_TOKEN: raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token") - asyncio.create_task(configure_telegram_webhook(payload.url)) + Utils.log_warning(f"START == webhook == CONFIG: {payload.url}") + await configure_telegram_webhook(payload.url) return WebhookResponse(status="ok", webhook_set_to=payload.url) @app.post("/webhook", description="Endpoint pour recevoir les mises à jour ou changement dans le bot Telegram") From b8fe59699b6373ad6fa45d3e52ee9c51800b233f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 15:06:30 +0000 Subject: [PATCH 42/88] Update webhook logging to show new and old URLs, and change log level to warning for webhook configuration --- Jenkinsfile | 3 ++- src/telegram_handler.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 41affa3..2a2abf3 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -83,9 +83,10 @@ pipeline { stage('Configure Webhook') { when { anyOf { - branch 'alwil17' + branch 'bradlab' branch 'dev' branch 'preprod' + branch 'prod' } } steps { diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 4644adb..6e92471 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -133,11 +133,11 @@ async def configure_telegram_webhook(webhook_url: str): bot = Bot(TELEGRAM_BOT_TOKEN) try: current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"===== TELEGRAM to connect to the WEBHOOK with ==== : {webhook_url} => {current_webhook.url}") + Utils.log_warning(f"===== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") if current_webhook.url != api_webhook_url: try: await bot.set_webhook(url=api_webhook_url) - Utils.log_info(f"Webhook Telegram configuré sur : {webhook_url}") + Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") except Exception as bot_error: Utils.log_error(f"Erreur configuration du webhook url : {e}") else: From 366ffcfa38fcae1ac67ec8f1e9f49aa5b78de515 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 15:58:19 +0000 Subject: [PATCH 43/88] Fix webhook URL formatting in Jenkinsfile and ensure proper shutdown of Telegram bot in main.py --- Jenkinsfile | 4 ++-- src/main.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2a2abf3..b596d58 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -106,10 +106,10 @@ pipeline { // Configure the webhook withCredentials([string(credentialsId: 'telegram-bot-token', variable: 'TELEGRAM_BOT_TOKEN')]) { sh """ - curl -X POST "${apiUrl}/set-webhook" \\ + curl -X POST "${apiUrl}set-webhook" \\ -H "Authorization: Bearer ${TELEGRAM_BOT_TOKEN}" \\ -H "Content-Type: application/json" \\ - -d '{ "url": "${apiUrl}/webhook" }' + -d '{ "url": "${apiUrl}webhook" }' """ } } diff --git a/src/main.py b/src/main.py index c1a5f9b..4b76bdc 100644 --- a/src/main.py +++ b/src/main.py @@ -47,7 +47,7 @@ async def app_lifespan(application: FastAPI): yield # L'application est maintenant prête à recevoir des requêtes Utils.log_info("Application KOZ API arrêtée.") - # await shutdown_ptb() + await shutdown_ptb() app = FastAPI( From 846504ac82de446fdf1133efe53c1f99baf5e20d Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 16:41:17 +0000 Subject: [PATCH 44/88] Comment out database save operations in start_command for debugging purposes --- src/telegram_handler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 6e92471..ccd0db3 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -35,10 +35,10 @@ async def start_command(update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") - try: - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) - except Exception as db_error: - Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") + # try: + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + # except Exception as db_error: + # Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") response_text = ( "Bonjour {user_name} ! Comment puis-je vous aider aujourd'hui ? Discutons ensemble. Voici quelques suggestions pour commencer :\n\n" "• Vous pouvez me poser une question sur un sujet qui vous intéresse.\n" @@ -49,10 +49,10 @@ async def start_command(update: Update, context): ) # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - try: - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") - except Exception as db_error: - Utils.log_error(f"DB_ERROR.start_command 2 ====== {e}") + # try: + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + # except Exception as db_error: + # Utils.log_error(f"DB_ERROR.start_command 2 ====== {e}") except Exception as e: Utils.log_error(f"Start command error ====== {e}") From 0d7ec556a5cf879e0e4184566f01188f81cb55ac Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 16:56:53 +0000 Subject: [PATCH 45/88] Refactor log messages in start_command and configure_telegram_webhook for consistency --- src/telegram_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index ccd0db3..793e8fb 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -54,7 +54,7 @@ async def start_command(update: Update, context): # except Exception as db_error: # Utils.log_error(f"DB_ERROR.start_command 2 ====== {e}") except Exception as e: - Utils.log_error(f"Start command error ====== {e}") + Utils.log_error(f"Start command error ==== {e}") async def handle_message(update: Update, context): @@ -133,7 +133,7 @@ async def configure_telegram_webhook(webhook_url: str): bot = Bot(TELEGRAM_BOT_TOKEN) try: current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"===== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") + Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") if current_webhook.url != api_webhook_url: try: await bot.set_webhook(url=api_webhook_url) From d6a53281e4d510204154318383760a87ecdbd02c Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 17:24:54 +0000 Subject: [PATCH 46/88] Comment out database save operations in handle_message for debugging purposes --- src/telegram_handler.py | 44 ++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 793e8fb..ba00146 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -66,11 +66,11 @@ async def handle_message(update: Update, context): message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - try: - Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - except Exception as db_error: - Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") + # try: + # Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # except Exception as db_error: + # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") try: chat_response = await asyncio.to_thread( @@ -85,26 +85,26 @@ async def handle_message(update: Update, context): ) response_text = chat_response.choices[0].message.content bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - try: - # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) - except Exception as db_error: - Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + # try: + # # Enregistrement dans DynamoDB + # await dynamodb_repo.save_message( + # chat_id, + # bot_message.message_id, + # ptb_app.bot.id, + # ptb_app.bot.username, + # response_text, + # "bot", + # MISTRAL_MODEL + # ) + # except Exception as db_error: + # Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - try: - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") - except Exception as db_error: - Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + # try: + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + # except Exception as db_error: + # Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: Utils.log_error("Traitement du message échoué.") From 801e4fb0faddedc046f72dabc0fa2ec586b1807f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 17:49:49 +0000 Subject: [PATCH 47/88] Refactor message handling in start_command and handle_message for improved clarity and error logging --- src/telegram_handler.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index ba00146..38b6eb7 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -40,7 +40,7 @@ async def start_command(update: Update, context): # except Exception as db_error: # Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") response_text = ( - "Bonjour {user_name} ! Comment puis-je vous aider aujourd'hui ? Discutons ensemble. Voici quelques suggestions pour commencer :\n\n" + f"Bonjour {user_name} ! Comment puis-je vous aider aujourd'hui ? Discutons ensemble. Voici quelques suggestions pour commencer :\n\n" "• Vous pouvez me poser une question sur un sujet qui vous intéresse.\n" "• Nous pouvons jouer à un jeu de mots, comme l'association d'idées ou le jeu des 20 questions.\n" "• Vous pouvez partager quelque chose sur vous, et j'essaierai de faire le lien.\n" @@ -68,7 +68,7 @@ async def handle_message(update: Update, context): # try: # Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") @@ -85,26 +85,26 @@ async def handle_message(update: Update, context): ) response_text = chat_response.choices[0].message.content bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # try: - # # Enregistrement dans DynamoDB - # await dynamodb_repo.save_message( - # chat_id, - # bot_message.message_id, - # ptb_app.bot.id, - # ptb_app.bot.username, - # response_text, - # "bot", - # MISTRAL_MODEL - # ) - # except Exception as db_error: - # Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + try: + # Enregistrement dans DynamoDB + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) + except Exception as db_error: + Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) # try: # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") # except Exception as db_error: - # Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + # Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: Utils.log_error("Traitement du message échoué.") From 6450a1ab6de5cc882d0e8760fe75ade81a02a78d Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:06:51 +0000 Subject: [PATCH 48/88] Translate welcome message and error responses to English for better user accessibility --- src/telegram_handler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 38b6eb7..70bc807 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -40,12 +40,12 @@ async def start_command(update: Update, context): # except Exception as db_error: # Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") response_text = ( - f"Bonjour {user_name} ! Comment puis-je vous aider aujourd'hui ? Discutons ensemble. Voici quelques suggestions pour commencer :\n\n" - "• Vous pouvez me poser une question sur un sujet qui vous intéresse.\n" - "• Nous pouvons jouer à un jeu de mots, comme l'association d'idées ou le jeu des 20 questions.\n" - "• Vous pouvez partager quelque chose sur vous, et j'essaierai de faire le lien.\n" - "• Nous pouvons discuter d'un événement récent ou d'un sujet d'actualité.\n\n" - "Par quoi souhaitez-vous commencer ?" + f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" + "• You can ask me a question about a topic you're interested in.\n" + "• We can play a word game, like word association or 20 questions.\n" + "• You can share something about yourself, and I'll do my best to relate.\n" + "• We can discuss a recent event or trending topic.\n\n" + "How would you like to begin?" ) # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) @@ -68,7 +68,7 @@ async def handle_message(update: Update, context): # try: # Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") @@ -99,7 +99,7 @@ async def handle_message(update: Update, context): except Exception as db_error: Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: - error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." + error_response = "Sorry, an error occurred while processing your message." bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) # try: # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") From 05305bbcc14e5d83dc90d683390ac8e89f585995 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:08:14 +0000 Subject: [PATCH 49/88] Enable database message saving in handle_message and add logging for process continuation --- src/telegram_handler.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 70bc807..f003cec 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -68,10 +68,11 @@ async def handle_message(update: Update, context): # try: # Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") - + Utils.log_warning(f"CONTINUE PROCESS ======= {user_name} - {user_message}") + try: chat_response = await asyncio.to_thread( mistral_client.chat.complete, From b4349ed2ffff0634c5585411941ffe162a66302d Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:08:41 +0000 Subject: [PATCH 50/88] Enable logging of user messages in handle_message for better traceability --- src/telegram_handler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index f003cec..1be98fe 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -67,11 +67,11 @@ async def handle_message(update: Update, context): user_name = user.full_name or user.username or "N/A" # try: - # Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") + Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") - Utils.log_warning(f"CONTINUE PROCESS ======= {user_name} - {user_message}") + Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") try: chat_response = await asyncio.to_thread( From 5516eb3ec3b57f54f952a95f44ff95e0f4b6e309 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:33:33 +0000 Subject: [PATCH 51/88] Add logging for message responses and conditional database saving in handle_message --- src/telegram_handler.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 1be98fe..b40852b 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -85,18 +85,25 @@ async def handle_message(update: Update, context): ] ) response_text = chat_response.choices[0].message.content + Utils.log_warning(f"MESSAGE ANSWER ======= {response_text}") + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") + try: - # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) + if not bot_message : + Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") + # Enregistrement dans DynamoDB + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) + Utils.log_warning(f"ANSWER SAVED =======") except Exception as db_error: Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: From 593f3f4ff42449a9c955a6ea85b7b669618f9d83 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:51:56 +0000 Subject: [PATCH 52/88] Add logging for Mistral response retrieval and conditional message saving in handle_message --- src/telegram_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index b40852b..11b8128 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -74,6 +74,7 @@ async def handle_message(update: Update, context): Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") try: + Utils.log_warning(f"GET MISTRAL RESPONSE ======") chat_response = await asyncio.to_thread( mistral_client.chat.complete, model=MISTRAL_MODEL, @@ -91,7 +92,7 @@ async def handle_message(update: Update, context): Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") try: - if not bot_message : + if bot_message : Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") # Enregistrement dans DynamoDB await dynamodb_repo.save_message( From 6d317ed5ea81893e609be46673162ccf5dccde0a Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 18:56:23 +0000 Subject: [PATCH 53/88] Refactor Mistral response retrieval to use synchronous call in handle_message --- src/telegram_handler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 11b8128..534fc88 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -75,8 +75,7 @@ async def handle_message(update: Update, context): try: Utils.log_warning(f"GET MISTRAL RESPONSE ======") - chat_response = await asyncio.to_thread( - mistral_client.chat.complete, + chat_response = mistral_client.chat.complete( model=MISTRAL_MODEL, messages=[ { From 5c49efe21088f36d9ada2e951cf8fbef6fe2f377 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:03:42 +0000 Subject: [PATCH 54/88] Add help and clear commands to handle_message for user assistance --- src/telegram_handler.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 534fc88..3ea3235 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -116,6 +116,29 @@ async def handle_message(update: Update, context): except Exception as e: Utils.log_error("Traitement du message échoué.") +async def help_command(update: Update, context): + try: + chat_id = update.message.chat_id + response_text = ( + "Voici les commandes disponibles :\n" + "/start - Afficher le menu principal\n" + "/help - Afficher l'aide\n" + "/clear - Effacer la conversation" + ) + await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Help command error ==== {e}") + +async def clear_command(update: Update, context): + try: + chat_id = update.message.chat_id + response_text = "La conversation a été effacée" + await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + # Ici tu peux ajouter la logique pour effacer l'historique si besoin + except Exception as e: + Utils.log_error(f"Clear command error ==== {e}") + + async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: Utils.log_error(f"== Unhandled Telegram exception: {context.error}") @@ -124,6 +147,8 @@ async def setup_ptb_handlers(): try: # Configure les handlers de l'application Python-Telegram-Bot ptb_app.add_handler(CommandHandler("start", start_command)) + ptb_app.add_handler(CommandHandler("help", help_command)) + ptb_app.add_handler(CommandHandler("clear", clear_command)) Utils.log_warning("Handle first message ====") ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) ptb_app.add_error_handler(_error_handler) From f287cf189d7f99e413aa00daefd010b31f23d1d2 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:06:04 +0000 Subject: [PATCH 55/88] Add reply keyboard markup to start command for improved user interaction --- src/telegram_handler.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 3ea3235..0c34bcc 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,4 +1,4 @@ -from telegram import Update, Bot +from telegram import Update, Bot, ReplyKeyboardMarkup from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes from mistralai import Mistral import asyncio @@ -35,6 +35,12 @@ async def start_command(update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") + # Menu clavier + reply_markup = ReplyKeyboardMarkup( + [["/start", "/help", "/clear"]], + resize_keyboard=True + ) + # try: # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) # except Exception as db_error: @@ -48,7 +54,7 @@ async def start_command(update: Update, context): "How would you like to begin?" ) # await update.message.reply_text() - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) # try: # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") # except Exception as db_error: From acc4f9ac0cab203f7781c4322d08e7cfc2f8c294 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:20:33 +0000 Subject: [PATCH 56/88] Add error logging in handle_message for better debugging --- src/telegram_handler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 0c34bcc..cb4e1eb 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -113,7 +113,8 @@ async def handle_message(update: Update, context): except Exception as db_error: Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: - error_response = "Sorry, an error occurred while processing your message." + error_response = "Sorry, an error occurred while processing your message. " + Utils.log_error(f"{error_response}: {e}") bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) # try: # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") From 029c4e1723ab16be42fbb21bba776145b68b5d86 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:21:28 +0000 Subject: [PATCH 57/88] Remove clear command from reply keyboard in start command for streamlined user options --- src/telegram_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index cb4e1eb..2fa9f05 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -37,7 +37,7 @@ async def start_command(update: Update, context): # Menu clavier reply_markup = ReplyKeyboardMarkup( - [["/start", "/help", "/clear"]], + [["/start", "/help"]], resize_keyboard=True ) From 56566bac5879c5f71da6d0c34cb33ff9bf8c1c97 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:38:59 +0000 Subject: [PATCH 58/88] Comment out message saving in handle_message for debugging purposes --- src/telegram_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 2fa9f05..d2ed3c1 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -74,7 +74,7 @@ async def handle_message(update: Update, context): # try: Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") From 0a183ffc698a3036011997024a3671a7f33e7198 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:43:55 +0000 Subject: [PATCH 59/88] Refactor handle_message to enable message saving and improve response handling --- src/telegram_handler.py | 47 ++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index d2ed3c1..89f3c14 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -74,7 +74,7 @@ async def handle_message(update: Update, context): # try: Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") @@ -90,28 +90,31 @@ async def handle_message(update: Update, context): }, ] ) - response_text = chat_response.choices[0].message.content - Utils.log_warning(f"MESSAGE ANSWER ======= {response_text}") + Utils.log_warning(f"AFTER MISTRAL ======= {chat_response}") - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") - - try: - if bot_message : - Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") - # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) - Utils.log_warning(f"ANSWER SAVED =======") - except Exception as db_error: - Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + if (chat_response): + response_text = chat_response.choices[0].message.content + Utils.log_warning(f"MESSAGE ANSWER ======= {response_text}") + + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") + + try: + if bot_message : + Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") + # Enregistrement dans DynamoDB + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) + Utils.log_warning(f"ANSWER SAVED =======") + except Exception as db_error: + Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") except Exception as e: error_response = "Sorry, an error occurred while processing your message. " Utils.log_error(f"{error_response}: {e}") From d0fdf62ae38a601a2d8aa7a56f3f7ad1ab71e453 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 19:54:02 +0000 Subject: [PATCH 60/88] Comment out message saving in handle_message for debugging purposes --- src/telegram_handler.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 89f3c14..0b91a78 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -74,7 +74,7 @@ async def handle_message(update: Update, context): # try: Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") # except Exception as db_error: # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") @@ -103,15 +103,15 @@ async def handle_message(update: Update, context): if bot_message : Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) + # await dynamodb_repo.save_message( + # chat_id, + # bot_message.message_id, + # ptb_app.bot.id, + # ptb_app.bot.username, + # response_text, + # "bot", + # MISTRAL_MODEL + # ) Utils.log_warning(f"ANSWER SAVED =======") except Exception as db_error: Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") From 4f9d061cbefe18f96ade3250d3f0cba5cfa976e7 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 20:07:30 +0000 Subject: [PATCH 61/88] Refactor handle_message to enable message saving in DynamoDB and improve error handling --- src/main.py | 1 + src/telegram_handler.py | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/main.py b/src/main.py index 4b76bdc..1b9384e 100644 --- a/src/main.py +++ b/src/main.py @@ -140,6 +140,7 @@ async def telegram_webhook(request: Request): try: update_json = await request.json() await process_telegram_update(update_json) + return {"status": "ok"} except Exception as e: Utils.log_error(f"Erreur de traitement du webhook: {e}") diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 0b91a78..5b5af15 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -99,22 +99,22 @@ async def handle_message(update: Update, context): bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") - try: - if bot_message : + if bot_message : + try: Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") # Enregistrement dans DynamoDB - # await dynamodb_repo.save_message( - # chat_id, - # bot_message.message_id, - # ptb_app.bot.id, - # ptb_app.bot.username, - # response_text, - # "bot", - # MISTRAL_MODEL - # ) - Utils.log_warning(f"ANSWER SAVED =======") - except Exception as db_error: - Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) + except Exception as db_error: + Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + Utils.log_warning(f"ANSWER SAVED =======") except Exception as e: error_response = "Sorry, an error occurred while processing your message. " Utils.log_error(f"{error_response}: {e}") From 016360fa48bc7f6d66c51f2f7e3742a4a2a25549 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 20:13:50 +0000 Subject: [PATCH 62/88] Refactor handle_message to restore message saving in DynamoDB and improve logging --- src/telegram_handler.py | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 5b5af15..132f7c4 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -72,11 +72,8 @@ async def handle_message(update: Update, context): message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - # try: Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - # except Exception as db_error: - # Utils.log_error(f"DB_ERROR.handle_message ====== {db_error}") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") try: @@ -99,21 +96,17 @@ async def handle_message(update: Update, context): bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") - if bot_message : - try: - Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") - # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) - except Exception as db_error: - Utils.log_info(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") + # Enregistrement dans DynamoDB + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + ptb_app.bot.id, + ptb_app.bot.username, + response_text, + "bot", + MISTRAL_MODEL + ) Utils.log_warning(f"ANSWER SAVED =======") except Exception as e: error_response = "Sorry, an error occurred while processing your message. " From f777be4bdf7c2ca6789207f3f5197b885b484383 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 20:17:05 +0000 Subject: [PATCH 63/88] Restore message saving in DynamoDB for start_command and improve error logging --- src/telegram_handler.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 132f7c4..53f3e66 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -41,10 +41,7 @@ async def start_command(update: Update, context): resize_keyboard=True ) - # try: - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) - # except Exception as db_error: - # Utils.log_error(f"DB_ERROR.start_command 1 ====== {e}") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) response_text = ( f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" "• You can ask me a question about a topic you're interested in.\n" @@ -55,10 +52,7 @@ async def start_command(update: Update, context): ) # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) - # try: - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") - # except Exception as db_error: - # Utils.log_error(f"DB_ERROR.start_command 2 ====== {e}") + await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") except Exception as e: Utils.log_error(f"Start command error ==== {e}") From 1caedc65b3ae9539a633d793cec9730b177355f9 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 21:10:07 +0000 Subject: [PATCH 64/88] Update DynamoDB permissions to allow all actions for the chatbot function --- infrastructure/template.yaml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 2fca501..4417461 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -112,19 +112,8 @@ Resources: TableName: !Ref DynamoDBTable - Statement: - Effect: Allow - Action: - - dynamodb:GetItem - - dynamodb:DeleteItem - - dynamodb:PutItem - - dynamodb:Query - - dynamodb:UpdateItem - - dynamodb:BatchWriteItem - - dynamodb:BatchGetItem - - dynamodb:DescribeTable - - dynamodb:ConditionCheckItem - Resource: - - !GetAtt DynamoDBTable.Arn - - !Sub "${DynamoDBTable.Arn}/index/*" + Action: "*" + Resource: "*" Events: Api: Type: HttpApi From 851df5bb9453d5b992cd9b09cab566490998f58b Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 21:12:48 +0000 Subject: [PATCH 65/88] Change logger name to standardize logging format --- src/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index ba76307..b22ebc2 100644 --- a/src/utils.py +++ b/src/utils.py @@ -8,7 +8,7 @@ ## Simple edit logging.basicConfig() -logger = logging.getLogger("chatbot-bradlab-logs") +logger = logging.getLogger("chatbot") class Utils: From a6b848ce06968e02e94bcccf2487cc4720a80976 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Tue, 3 Jun 2025 21:54:47 +0000 Subject: [PATCH 66/88] Update DynamoDB permissions to specify allowed actions and comment out message saving in Telegram handler --- infrastructure/template.yaml | 11 ++++++++++- src/config.py | 2 +- src/telegram_handler.py | 32 +++++++++++++------------------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 4417461..4ff7c7e 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -112,7 +112,16 @@ Resources: TableName: !Ref DynamoDBTable - Statement: - Effect: Allow - Action: "*" + Action: + - dynamodb:GetItem + - dynamodb:DeleteItem + - dynamodb:PutItem + - dynamodb:Query + - dynamodb:UpdateItem + - dynamodb:BatchWriteItem + - dynamodb:BatchGetItem + - dynamodb:DescribeTable + - dynamodb:ConditionCheckItem Resource: "*" Events: Api: diff --git a/src/config.py b/src/config.py index e97b895..95b64a1 100644 --- a/src/config.py +++ b/src/config.py @@ -7,7 +7,7 @@ class Settings(BaseSettings): ENV_NAME: str = "local" AWS_REGION_NAME: str = "" DYNAMO_TABLE: str = "" - # AWS_PROFILE: str = "" + AWS_PROFILE: str = "" MISTRAL_API_KEY: str = "" TELEGRAM_BOT_TOKEN: str = "" TELEGRAM_API_URL: str = "" diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 53f3e66..f08e923 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -41,7 +41,7 @@ async def start_command(update: Update, context): resize_keyboard=True ) - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) response_text = ( f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" "• You can ask me a question about a topic you're interested in.\n" @@ -52,7 +52,7 @@ async def start_command(update: Update, context): ) # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) - await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") except Exception as e: Utils.log_error(f"Start command error ==== {e}") @@ -67,7 +67,7 @@ async def handle_message(update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") try: @@ -85,31 +85,25 @@ async def handle_message(update: Update, context): if (chat_response): response_text = chat_response.choices[0].message.content - Utils.log_warning(f"MESSAGE ANSWER ======= {response_text}") bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - Utils.log_warning(f"ANSWER SENT ======= {bot_message.message_id}") - Utils.log_warning(f"SAVING ANSWER ======= {bot_message.message_id}") # Enregistrement dans DynamoDB - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - ptb_app.bot.id, - ptb_app.bot.username, - response_text, - "bot", - MISTRAL_MODEL - ) + # await dynamodb_repo.save_message( + # chat_id, + # bot_message.message_id, + # ptb_app.bot.id, + # ptb_app.bot.username, + # response_text, + # "bot", + # MISTRAL_MODEL + # ) Utils.log_warning(f"ANSWER SAVED =======") except Exception as e: error_response = "Sorry, an error occurred while processing your message. " Utils.log_error(f"{error_response}: {e}") bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - # try: - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") - # except Exception as db_error: - # Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {db_error}") + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") except Exception as e: Utils.log_error("Traitement du message échoué.") From ad30e4cdde9fb7c6982fdefd5fd1ed4fdab68b0a Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Wed, 4 Jun 2025 23:44:16 +0000 Subject: [PATCH 67/88] Refactor message handling to improve user message logging and bot response saving in DynamoDB --- src/dynamodb_repository.py | 5 +- src/telegram_handler.py | 120 ++++++++++++++++++++++--------------- 2 files changed, 75 insertions(+), 50 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index b992674..484e512 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -58,11 +58,12 @@ async def save_message( item['ai_model'] = ai_model # Exécute l'opération put_item (synchrone) dans un thread séparé - await asyncio.to_thread(self.table.put_item, Item=item) - Utils.log_info(f"Message enregistré dans DynamoDB: chat_id={chat_id}, role={role}") + return await asyncio.to_thread(self.table.put_item, Item=item) + # Utils.log_info(f"Message enregistré dans DynamoDB: chat_id={chat_id}, role={role}") except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}") # L'erreur n'est pas levée pour ne pas interrompre le flux du bot + return async def get_chat_history(self, chat_id: int, limit: int = 100) -> list[dict]: """ diff --git a/src/telegram_handler.py b/src/telegram_handler.py index f08e923..891ea41 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -41,7 +41,14 @@ async def start_command(update: Update, context): resize_keyboard=True ) - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, "user", user_message) + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=message_id, + user_id=str(user.id), + user_name=user_name, + text=user_message, + role="user" + ) response_text = ( f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" "• You can ask me a question about a topic you're interested in.\n" @@ -53,59 +60,76 @@ async def start_command(update: Update, context): # await update.message.reply_text() bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=bot_message.message_id, + user_id=str(ptb_app.bot.id), + user_name=ptb_app.bot.username, + text=response_text, + role="bot" + ) except Exception as e: Utils.log_error(f"Start command error ==== {e}") -async def handle_message(update: Update, context): +async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): try: - if update.message and update.message.text: - user = update.message.from_user - user_message = update.message.text - chat_id = update.message.chat_id - message_id = update.message.message_id - user_name = user.full_name or user.username or "N/A" - - Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") - - try: - Utils.log_warning(f"GET MISTRAL RESPONSE ======") - chat_response = mistral_client.chat.complete( - model=MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] - ) - Utils.log_warning(f"AFTER MISTRAL ======= {chat_response}") - - if (chat_response): - response_text = chat_response.choices[0].message.content - - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - - # Enregistrement dans DynamoDB - # await dynamodb_repo.save_message( - # chat_id, - # bot_message.message_id, - # ptb_app.bot.id, - # ptb_app.bot.username, - # response_text, - # "bot", - # MISTRAL_MODEL - # ) - Utils.log_warning(f"ANSWER SAVED =======") - except Exception as e: - error_response = "Sorry, an error occurred while processing your message. " - Utils.log_error(f"{error_response}: {e}") - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, error_response, "bot") + if update.message is None: + return + message = update.message + if not message: + return # Ignore les événements sans message (ex : edits, joins) + + user = message.from_user + if user is None or user.is_bot: + return # Ignore les messages envoyés par des bots, y compris lui-même + + chat_id = str(message.chat_id) + message_id = str(message.message_id) + user_name = user.username or f"{user.first_name} {user.last_name or ''}" + user_message = message.text + + # (1) Enregistrement du message utilisateur + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=message_id, + user_id=str(user.id), + user_name=user_name, + text=user_message, + role="user" + ) + + # (2) Appel à Mistral + Utils.log_warning(f"MISTRAL ==== {user_message} ") + chat_response = mistral_client.chat.complete( + model=MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ) + bot_reply = chat_response.choices[0].message.content + + # (3) Envoi de la réponse du bot + bot_message = await context.bot.send_message( + chat_id=chat_id, + text=bot_reply + ) + + # (4) Enregistrement du message du bot (mais on précise bien le role) + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=str(bot_message.message_id), + user_id=str(context.bot.id), + user_name=context.bot.username or "bot", + text=bot_reply, + role="bot" + ) + except Exception as e: - Utils.log_error("Traitement du message échoué.") + Utils.log_error(f"[handle_message] Erreur: {e}") async def help_command(update: Update, context): try: From 2df0f930186dd6a49226d6fe41e27cd1de8a038f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:15:57 +0000 Subject: [PATCH 68/88] Refactor Telegram handler to improve structure and logging; consolidate message handling and command setup --- src/main.py | 22 +-- src/telegram_handler copy.py | 203 ++++++++++++++++++++ src/telegram_handler.py | 356 ++++++++++++++++------------------- 3 files changed, 376 insertions(+), 205 deletions(-) create mode 100644 src/telegram_handler copy.py diff --git a/src/main.py b/src/main.py index 1b9384e..ec4fb95 100644 --- a/src/main.py +++ b/src/main.py @@ -12,18 +12,18 @@ from typing import Optional from .config import env_vars -import asyncio +from .telegram_handler import telegram_handler from .dynamodb_repository import dynamodb_repo # Importe les fonctions de traitement Telegram -from .telegram_handler import ( - setup_ptb_handlers, - configure_telegram_webhook, - process_telegram_update, - shutdown_ptb -) +# from .telegram_handler import ( +# setup_ptb_handlers, +# configure_telegram_webhook, +# process_telegram_update, +# shutdown_ptb +# ) from .config import env_vars @@ -41,13 +41,13 @@ class WebhookRequest(BaseModel): @asynccontextmanager async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") - await setup_ptb_handlers() + await telegram_handler.setup_ptb_handlers() # asyncio.create_task(configure_telegram_webhook()) yield # L'application est maintenant prête à recevoir des requêtes Utils.log_info("Application KOZ API arrêtée.") - await shutdown_ptb() + await telegram_handler.shutdown_ptb() app = FastAPI( @@ -132,14 +132,14 @@ async def set_webhook( raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Invalid token") Utils.log_warning(f"START == webhook == CONFIG: {payload.url}") - await configure_telegram_webhook(payload.url) + await telegram_handler.configure_telegram_webhook(payload.url) return WebhookResponse(status="ok", webhook_set_to=payload.url) @app.post("/webhook", description="Endpoint pour recevoir les mises à jour ou changement dans le bot Telegram") async def telegram_webhook(request: Request): try: update_json = await request.json() - await process_telegram_update(update_json) + await telegram_handler.process_telegram_update(update_json) return {"status": "ok"} except Exception as e: diff --git a/src/telegram_handler copy.py b/src/telegram_handler copy.py new file mode 100644 index 0000000..891ea41 --- /dev/null +++ b/src/telegram_handler copy.py @@ -0,0 +1,203 @@ +from telegram import Update, Bot, ReplyKeyboardMarkup +from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes +from mistralai import Mistral +import asyncio + +from .dynamodb_repository import dynamodb_repo +from .config import env_vars +from .utils import Utils + +api_key = env_vars.MISTRAL_API_KEY +# Récupérer les jetons depuis les variables d'environnement +TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN +MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY +# API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={env_vars.WEBHOOK_URL}" +MISTRAL_MODEL = "mistral-large-latest" + +if not TELEGRAM_BOT_TOKEN: + raise ValueError("TELEGRAM_BOT_TOKEN n'est pas défini.") +if not MISTRAL_API_KEY: + raise ValueError("MISTRAL_API_KEY n'est pas défini.") + +# Initialisation du client MistralAI +mistral_client = Mistral(api_key=MISTRAL_API_KEY) + +# Initialisation de l'application Python-Telegram-Bot +ptb_app = Application.builder().token(TELEGRAM_BOT_TOKEN).updater(None).build() + +async def start_command(update: Update, context): + try: + # Gère la commande /start. + user = update.message.from_user + user_message = update.message.text + chat_id = update.message.chat_id + message_id = update.message.message_id + user_name = user.full_name or user.username or "N/A" + Utils.log_warning(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") + + # Menu clavier + reply_markup = ReplyKeyboardMarkup( + [["/start", "/help"]], + resize_keyboard=True + ) + + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=message_id, + user_id=str(user.id), + user_name=user_name, + text=user_message, + role="user" + ) + response_text = ( + f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" + "• You can ask me a question about a topic you're interested in.\n" + "• We can play a word game, like word association or 20 questions.\n" + "• You can share something about yourself, and I'll do my best to relate.\n" + "• We can discuss a recent event or trending topic.\n\n" + "How would you like to begin?" + ) + # await update.message.reply_text() + bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) + # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=bot_message.message_id, + user_id=str(ptb_app.bot.id), + user_name=ptb_app.bot.username, + text=response_text, + role="bot" + ) + except Exception as e: + Utils.log_error(f"Start command error ==== {e}") + + +async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): + try: + if update.message is None: + return + message = update.message + if not message: + return # Ignore les événements sans message (ex : edits, joins) + + user = message.from_user + if user is None or user.is_bot: + return # Ignore les messages envoyés par des bots, y compris lui-même + + chat_id = str(message.chat_id) + message_id = str(message.message_id) + user_name = user.username or f"{user.first_name} {user.last_name or ''}" + user_message = message.text + + # (1) Enregistrement du message utilisateur + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=message_id, + user_id=str(user.id), + user_name=user_name, + text=user_message, + role="user" + ) + + # (2) Appel à Mistral + Utils.log_warning(f"MISTRAL ==== {user_message} ") + chat_response = mistral_client.chat.complete( + model=MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ) + bot_reply = chat_response.choices[0].message.content + + # (3) Envoi de la réponse du bot + bot_message = await context.bot.send_message( + chat_id=chat_id, + text=bot_reply + ) + + # (4) Enregistrement du message du bot (mais on précise bien le role) + await dynamodb_repo.save_message( + chat_id=chat_id, + message_id=str(bot_message.message_id), + user_id=str(context.bot.id), + user_name=context.bot.username or "bot", + text=bot_reply, + role="bot" + ) + + except Exception as e: + Utils.log_error(f"[handle_message] Erreur: {e}") + +async def help_command(update: Update, context): + try: + chat_id = update.message.chat_id + response_text = ( + "Voici les commandes disponibles :\n" + "/start - Afficher le menu principal\n" + "/help - Afficher l'aide\n" + "/clear - Effacer la conversation" + ) + await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Help command error ==== {e}") + +async def clear_command(update: Update, context): + try: + chat_id = update.message.chat_id + response_text = "La conversation a été effacée" + await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + # Ici tu peux ajouter la logique pour effacer l'historique si besoin + except Exception as e: + Utils.log_error(f"Clear command error ==== {e}") + + +async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: + Utils.log_error(f"== Unhandled Telegram exception: {context.error}") + + +async def setup_ptb_handlers(): + try: + # Configure les handlers de l'application Python-Telegram-Bot + ptb_app.add_handler(CommandHandler("start", start_command)) + ptb_app.add_handler(CommandHandler("help", help_command)) + ptb_app.add_handler(CommandHandler("clear", clear_command)) + Utils.log_warning("Handle first message ====") + ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) + ptb_app.add_error_handler(_error_handler) + await ptb_app.initialize() + Utils.log_warning("Handlers Telegram initialisés.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") + +async def configure_telegram_webhook(webhook_url: str): + api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" + if not api_webhook_url: + Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") + return + + bot = Bot(TELEGRAM_BOT_TOKEN) + try: + current_webhook = await bot.get_webhook_info() + Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") + if current_webhook.url != api_webhook_url: + try: + await bot.set_webhook(url=api_webhook_url) + Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") + except Exception as bot_error: + Utils.log_error(f"Erreur configuration du webhook url : {e}") + else: + Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") + +async def process_telegram_update(update_json: dict): + update = Update.de_json(update_json, ptb_app.bot) + await ptb_app.process_update(update) + +async def shutdown_ptb(): + # Arrête l'application Python-Telegram-Bot. + await ptb_app.shutdown() + Utils.log_warning("Application Telegram arrêtée.") \ No newline at end of file diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 891ea41..f5550ca 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,203 +1,171 @@ from telegram import Update, Bot, ReplyKeyboardMarkup from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes from mistralai import Mistral -import asyncio from .dynamodb_repository import dynamodb_repo from .config import env_vars from .utils import Utils -api_key = env_vars.MISTRAL_API_KEY -# Récupérer les jetons depuis les variables d'environnement -TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN -MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY -# API_WEBHOOK_URL = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={env_vars.WEBHOOK_URL}" -MISTRAL_MODEL = "mistral-large-latest" - -if not TELEGRAM_BOT_TOKEN: - raise ValueError("TELEGRAM_BOT_TOKEN n'est pas défini.") -if not MISTRAL_API_KEY: - raise ValueError("MISTRAL_API_KEY n'est pas défini.") - -# Initialisation du client MistralAI -mistral_client = Mistral(api_key=MISTRAL_API_KEY) - -# Initialisation de l'application Python-Telegram-Bot -ptb_app = Application.builder().token(TELEGRAM_BOT_TOKEN).updater(None).build() - -async def start_command(update: Update, context): - try: - # Gère la commande /start. - user = update.message.from_user - user_message = update.message.text - chat_id = update.message.chat_id - message_id = update.message.message_id - user_name = user.full_name or user.username or "N/A" - Utils.log_warning(f"===== Start command Initializing ====== {TELEGRAM_BOT_TOKEN}") - - # Menu clavier - reply_markup = ReplyKeyboardMarkup( - [["/start", "/help"]], - resize_keyboard=True - ) - - await dynamodb_repo.save_message( - chat_id=chat_id, - message_id=message_id, - user_id=str(user.id), - user_name=user_name, - text=user_message, - role="user" - ) - response_text = ( - f"Hello {user_name}! How can I assist you today? Let's have a friendly conversation. Here are a few suggestions for how we can proceed:\n\n" - "• You can ask me a question about a topic you're interested in.\n" - "• We can play a word game, like word association or 20 questions.\n" - "• You can share something about yourself, and I'll do my best to relate.\n" - "• We can discuss a recent event or trending topic.\n\n" - "How would you like to begin?" - ) - # await update.message.reply_text() - bot_message = await ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) - # await dynamodb_repo.save_message(chat_id, bot_message.message_id, ptb_app.bot.id, ptb_app.bot.username, response_text, "bot") - await dynamodb_repo.save_message( - chat_id=chat_id, - message_id=bot_message.message_id, - user_id=str(ptb_app.bot.id), - user_name=ptb_app.bot.username, - text=response_text, - role="bot" - ) - except Exception as e: - Utils.log_error(f"Start command error ==== {e}") - - -async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE): - try: - if update.message is None: +class TelegramHandler: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(TelegramHandler, cls).__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self): + if self._initialized: return - message = update.message - if not message: - return # Ignore les événements sans message (ex : edits, joins) - - user = message.from_user - if user is None or user.is_bot: - return # Ignore les messages envoyés par des bots, y compris lui-même - - chat_id = str(message.chat_id) - message_id = str(message.message_id) - user_name = user.username or f"{user.first_name} {user.last_name or ''}" - user_message = message.text - - # (1) Enregistrement du message utilisateur - await dynamodb_repo.save_message( - chat_id=chat_id, - message_id=message_id, - user_id=str(user.id), - user_name=user_name, - text=user_message, - role="user" - ) - - # (2) Appel à Mistral - Utils.log_warning(f"MISTRAL ==== {user_message} ") - chat_response = mistral_client.chat.complete( - model=MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] - ) - bot_reply = chat_response.choices[0].message.content - - # (3) Envoi de la réponse du bot - bot_message = await context.bot.send_message( - chat_id=chat_id, - text=bot_reply - ) - - # (4) Enregistrement du message du bot (mais on précise bien le role) - await dynamodb_repo.save_message( - chat_id=chat_id, - message_id=str(bot_message.message_id), - user_id=str(context.bot.id), - user_name=context.bot.username or "bot", - text=bot_reply, - role="bot" - ) - - except Exception as e: - Utils.log_error(f"[handle_message] Erreur: {e}") - -async def help_command(update: Update, context): - try: - chat_id = update.message.chat_id - response_text = ( - "Voici les commandes disponibles :\n" - "/start - Afficher le menu principal\n" - "/help - Afficher l'aide\n" - "/clear - Effacer la conversation" - ) - await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - except Exception as e: - Utils.log_error(f"Help command error ==== {e}") - -async def clear_command(update: Update, context): - try: - chat_id = update.message.chat_id - response_text = "La conversation a été effacée" - await ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # Ici tu peux ajouter la logique pour effacer l'historique si besoin - except Exception as e: - Utils.log_error(f"Clear command error ==== {e}") - - -async def _error_handler(update: object, context: ContextTypes.DEFAULT_TYPE) -> None: - Utils.log_error(f"== Unhandled Telegram exception: {context.error}") - - -async def setup_ptb_handlers(): - try: - # Configure les handlers de l'application Python-Telegram-Bot - ptb_app.add_handler(CommandHandler("start", start_command)) - ptb_app.add_handler(CommandHandler("help", help_command)) - ptb_app.add_handler(CommandHandler("clear", clear_command)) - Utils.log_warning("Handle first message ====") - ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message)) - ptb_app.add_error_handler(_error_handler) - await ptb_app.initialize() - Utils.log_warning("Handlers Telegram initialisés.") - except Exception as e: - Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") - -async def configure_telegram_webhook(webhook_url: str): - api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" - if not api_webhook_url: - Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") - return - - bot = Bot(TELEGRAM_BOT_TOKEN) - try: - current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") - if current_webhook.url != api_webhook_url: - try: - await bot.set_webhook(url=api_webhook_url) - Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") - except Exception as bot_error: - Utils.log_error(f"Erreur configuration du webhook url : {e}") - else: - Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") - except Exception as e: - Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") - -async def process_telegram_update(update_json: dict): - update = Update.de_json(update_json, ptb_app.bot) - await ptb_app.process_update(update) - -async def shutdown_ptb(): - # Arrête l'application Python-Telegram-Bot. - await ptb_app.shutdown() - Utils.log_warning("Application Telegram arrêtée.") \ No newline at end of file + self.api_key = env_vars.MISTRAL_API_KEY + self.TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN + self.MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY + self.MISTRAL_MODEL = "mistral-large-latest" + + if not self.TELEGRAM_BOT_TOKEN: + raise ValueError("TELEGRAM_BOT_TOKEN n'est pas défini.") + if not self.MISTRAL_API_KEY: + raise ValueError("MISTRAL_API_KEY n'est pas défini.") + + self.mistral_client = Mistral(api_key=self.MISTRAL_API_KEY) + self.ptb_app = Application.builder().token(self.TELEGRAM_BOT_TOKEN).updater(None).build() + self._initialized = True + + async def start_command(self, update: Update, context): + try: + user = update.message.from_user + chat_id = update.message.chat_id + user_name = user.full_name or user.username or "N/A" + Utils.log_warning(f"===== Start command Initializing ====== {self.TELEGRAM_BOT_TOKEN}") + + reply_markup = ReplyKeyboardMarkup( + [["/start", "/help", "/clear"]], + resize_keyboard=True + ) + + response_text = ( + f"Bonjour {user_name} !\n" + "Bienvenue sur le bot KOZ.\n" + "Utilisez le menu ci-dessous pour commencer :" + ) + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) + except Exception as e: + Utils.log_error(f"Start command error ==== {e}") + + async def handle_message(self, update: Update, context): + try: + if update.message and update.message.text: + user = update.message.from_user + user_message = update.message.text + chat_id = update.message.chat_id + message_id = update.message.message_id + user_name = user.full_name or user.username or "N/A" + + Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") + + try: + Utils.log_warning(f"GET MISTRAL RESPONSE ======") + chat_response = self.mistral_client.chat.complete( + model=self.MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ) + Utils.log_warning(f"AFTER MISTRAL ====== {chat_response}") + + if chat_response: + response_text = chat_response.choices[0].message.content + + bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + self.ptb_app.bot.id, + self.ptb_app.bot.username, + response_text, + "bot", + self.MISTRAL_MODEL + ) + Utils.log_warning(f"ANSWER SAVED =======") + except Exception as e: + error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." + Utils.log_error(f"{error_response}: {e}") + await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + except Exception as e: + Utils.log_error("Traitement du message échoué.") + + async def help_command(self, update: Update, context): + try: + chat_id = update.message.chat_id + response_text = ( + "Voici les commandes disponibles :\n" + "/start - Afficher le menu principal\n" + "/help - Afficher l'aide\n" + "/clear - Effacer la conversation" + ) + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Help command error ==== {e}") + + async def clear_command(self, update: Update, context): + try: + chat_id = update.message.chat_id + response_text = "La conversation a été effacée" + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Clear command error ==== {e}") + + async def _error_handler(self, update: object, context: ContextTypes.DEFAULT_TYPE) -> None: + Utils.log_error(f"== Unhandled Telegram exception: {context.error}") + + async def setup_ptb_handlers(self): + try: + self.ptb_app.add_handler(CommandHandler("start", self.start_command)) + self.ptb_app.add_handler(CommandHandler("help", self.help_command)) + self.ptb_app.add_handler(CommandHandler("clear", self.clear_command)) + Utils.log_warning("Handle first message ====") + self.ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)) + self.ptb_app.add_error_handler(self._error_handler) + await self.ptb_app.initialize() + Utils.log_warning("Handlers Telegram initialisés.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") + + async def configure_telegram_webhook(self, webhook_url: str): + api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{self.TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" + if not api_webhook_url: + Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") + return + + bot = Bot(self.TELEGRAM_BOT_TOKEN) + try: + current_webhook = await bot.get_webhook_info() + Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") + if current_webhook.url != api_webhook_url: + try: + await bot.set_webhook(url=api_webhook_url) + Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") + except Exception as bot_error: + Utils.log_error(f"Erreur configuration du webhook url : {bot_error}") + else: + Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") + + async def process_telegram_update(self, update_json: dict): + update = Update.de_json(update_json, self.ptb_app.bot) + await self.ptb_app.process_update(update) + + async def shutdown_ptb(self): + await self.ptb_app.shutdown() + Utils.log_warning("Application Telegram arrêtée.") + +# Singleton instance +telegram_handler = TelegramHandler() From fd0053d9dcea426c81e9d687554fcc45a7071d6f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:19:30 +0000 Subject: [PATCH 69/88] Initialize setup of PTB handlers in TelegramHandler constructor --- src/main.py | 2 +- src/telegram_handler.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.py b/src/main.py index ec4fb95..4d3cb14 100644 --- a/src/main.py +++ b/src/main.py @@ -41,7 +41,7 @@ class WebhookRequest(BaseModel): @asynccontextmanager async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") - await telegram_handler.setup_ptb_handlers() + # await telegram_handler.setup_ptb_handlers() # asyncio.create_task(configure_telegram_webhook()) yield # L'application est maintenant prête à recevoir des requêtes diff --git a/src/telegram_handler.py b/src/telegram_handler.py index f5550ca..ae676f0 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -31,6 +31,8 @@ def __init__(self): self.mistral_client = Mistral(api_key=self.MISTRAL_API_KEY) self.ptb_app = Application.builder().token(self.TELEGRAM_BOT_TOKEN).updater(None).build() self._initialized = True + self.setup_ptb_handlers() + async def start_command(self, update: Update, context): try: From 36da2ca5887ce59ce58e63eb29ede621d262c7dd Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:33:16 +0000 Subject: [PATCH 70/88] Enable setup of PTB handlers in application lifespan and comment out direct call in TelegramHandler constructor --- src/main.py | 2 +- src/telegram_handler.py | 64 +++++++++++++++++++---------------------- 2 files changed, 31 insertions(+), 35 deletions(-) diff --git a/src/main.py b/src/main.py index 4d3cb14..ec4fb95 100644 --- a/src/main.py +++ b/src/main.py @@ -41,7 +41,7 @@ class WebhookRequest(BaseModel): @asynccontextmanager async def app_lifespan(application: FastAPI): Utils.log_info("Application KOZ API démarrée.") - # await telegram_handler.setup_ptb_handlers() + await telegram_handler.setup_ptb_handlers() # asyncio.create_task(configure_telegram_webhook()) yield # L'application est maintenant prête à recevoir des requêtes diff --git a/src/telegram_handler.py b/src/telegram_handler.py index ae676f0..4bbb922 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -31,7 +31,7 @@ def __init__(self): self.mistral_client = Mistral(api_key=self.MISTRAL_API_KEY) self.ptb_app = Application.builder().token(self.TELEGRAM_BOT_TOKEN).updater(None).build() self._initialized = True - self.setup_ptb_handlers() + # self.setup_ptb_handlers() async def start_command(self, update: Update, context): @@ -66,42 +66,38 @@ async def handle_message(self, update: Update, context): Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - Utils.log_warning(f"CONTINUE PROCESS ======= {user_name}") - try: - Utils.log_warning(f"GET MISTRAL RESPONSE ======") - chat_response = self.mistral_client.chat.complete( - model=self.MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] + Utils.log_warning(f"GET MISTRAL RESPONSE ======") + chat_response = self.mistral_client.chat.complete( + model=self.MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ) + Utils.log_warning(f"AFTER MISTRAL ======") + + if chat_response: + response_text = chat_response.choices[0].message.content + + bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + self.ptb_app.bot.id, + self.ptb_app.bot.username, + response_text, + "bot", + self.MISTRAL_MODEL ) - Utils.log_warning(f"AFTER MISTRAL ====== {chat_response}") - - if chat_response: - response_text = chat_response.choices[0].message.content - - bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - self.ptb_app.bot.id, - self.ptb_app.bot.username, - response_text, - "bot", - self.MISTRAL_MODEL - ) - Utils.log_warning(f"ANSWER SAVED =======") - except Exception as e: - error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." - Utils.log_error(f"{error_response}: {e}") - await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + Utils.log_warning(f"ANSWER SAVED =======") except Exception as e: - Utils.log_error("Traitement du message échoué.") + error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." + await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + Utils.log_error(f"[handle_message] Erreur: {e}") async def help_command(self, update: Update, context): try: From bdd4f4979b62fd53e7f3b5fa9e19df2d3e35510d Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:36:51 +0000 Subject: [PATCH 71/88] Refactor test fixtures to improve mocking of environment variables and Telegram handler --- tests/test_main.py | 48 ++++++++++++++-------------------------------- 1 file changed, 14 insertions(+), 34 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 5455da6..4c2776d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -9,46 +9,34 @@ def mock_env_vars(): """ Mocke les variables d'environnement requises par l'application. - Utilise scope="module" et autouse=True pour que le mocking s'applique - à tous les tests de ce module et soit setup/teardown une seule fois. """ - # Crée une instance mockée de Settings mock_settings_instance = MagicMock(spec=Settings) mock_settings_instance.TELEGRAM_BOT_TOKEN = "mock_telegram_token_for_tests" mock_settings_instance.MISTRAL_API_KEY = "mock_mistral_api_key_for_tests" mock_settings_instance.WEBHOOK_URL = "http://mock.webhook.url/webhook_for_tests" - mock_settings_instance.TELEGRAM_API_URL = "https://api.telegram.org" # Ajoutez toutes les vars nécessaires + mock_settings_instance.TELEGRAM_API_URL = "https://api.telegram.org" with patch('src.config.get_settings', return_value=mock_settings_instance): - # Ici, en mockant get_settings, on contourne complètement la lecture du .env. yield @pytest.fixture(scope="module", autouse=True) -def mock_telegram_handler_module(): +def mock_telegram_handler_singleton(): """ - Mocke complètement le module 'src.telegram_handler' pour éviter - son exécution réelle lors de l'importation de 'main.py'. + Mocke le singleton telegram_handler pour éviter l'exécution réelle. """ - # Crée un mock pour le module telegram_handler. - # Nous pourrions ajouter des attributs ou méthodes mockés si main.py - # appelait des choses spécifiques du handler directement (ex: handler.process_update) mock_handler = MagicMock() - - # Mocker les fonctions/objets que main.py importe depuis telegram_handler.py - # Ces noms doivent correspondre exactement à ce qui est importé dans main.py - mock_handler.setup_ptb_handlers = AsyncMock(return_value=None) # async function, so it will be awaited - mock_handler.configure_telegram_webhook = AsyncMock(return_value=None) # async function - mock_handler.process_telegram_update = AsyncMock(return_value=None) # async function - mock_handler.shutdown_ptb = AsyncMock(return_value=None) # async function - - with patch.dict('sys.modules', {'src.telegram_handler': mock_handler}): - yield # L'application va maintenant importer notre mock_handler - + # Mock les méthodes async attendues sur le singleton + mock_handler.setup_ptb_handlers = AsyncMock(return_value=None) + mock_handler.configure_telegram_webhook = AsyncMock(return_value=None) + mock_handler.process_telegram_update = AsyncMock(return_value=None) + mock_handler.shutdown_ptb = AsyncMock(return_value=None) + + # Patch le singleton dans le module src.telegram_handler + with patch('src.telegram_handler.telegram_handler', mock_handler): + yield @pytest.fixture(scope="module") def client(): - # C'est ici que src.main est importé, et donc src.telegram_handler - # sera importé comme notre mock_handler. from src.main import app with TestClient(app) as c: yield c @@ -57,7 +45,7 @@ def test_read_main(client): response = client.get("/") assert response.status_code == 200 assert response.json() == {"msg": "Hello World. Welcome to KOZ API"} - + def test_read_prompt(client): response = client.get("/prompt") assert response.status_code == 404 @@ -68,7 +56,6 @@ def test_noread_prompt(client): assert response.status_code == 404 assert response.json() != {"msg": "Hola", "response": ""} - # calculator.py class Calculator: def add(self, a, b): @@ -79,38 +66,31 @@ def divide(self, a, b): raise ValueError("Division by zero is not allowed") return a / b - - @pytest.fixture def calculator(): return Calculator() - def test_add_positive_numbers(calculator): result = calculator.add(2, 3) assert result == 5 - def test_add_negative_numbers(calculator): result = calculator.add(-1, -4) assert result == -5 - def test_add_zero(calculator): result = calculator.add(10, 0) assert result == 10 - def test_divide_valid_numbers(calculator): result = calculator.divide(10, 2) assert result == 5.0 - def test_divide_by_zero(calculator): with pytest.raises(ValueError, match="Division by zero is not allowed"): calculator.divide(10, 0) - def test_divide_negative_numbers(calculator): result = calculator.divide(-10, 2) assert result == -5.0 + \ No newline at end of file From 0e1b28264540cf66e13ff937656a6737d4a3fdd2 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:42:21 +0000 Subject: [PATCH 72/88] Refactor tests to enhance Telegram command handling and improve webhook response validation --- tests/test_main.py | 53 ++++---------- tests/test_telegram_handler.py | 123 ++++++++++++++++----------------- 2 files changed, 75 insertions(+), 101 deletions(-) diff --git a/tests/test_main.py b/tests/test_main.py index 4c2776d..17b1d94 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -56,41 +56,18 @@ def test_noread_prompt(client): assert response.status_code == 404 assert response.json() != {"msg": "Hola", "response": ""} -# calculator.py -class Calculator: - def add(self, a, b): - return a + b - - def divide(self, a, b): - if b == 0: - raise ValueError("Division by zero is not allowed") - return a / b - -@pytest.fixture -def calculator(): - return Calculator() - -def test_add_positive_numbers(calculator): - result = calculator.add(2, 3) - assert result == 5 - -def test_add_negative_numbers(calculator): - result = calculator.add(-1, -4) - assert result == -5 - -def test_add_zero(calculator): - result = calculator.add(10, 0) - assert result == 10 - -def test_divide_valid_numbers(calculator): - result = calculator.divide(10, 2) - assert result == 5.0 - -def test_divide_by_zero(calculator): - with pytest.raises(ValueError, match="Division by zero is not allowed"): - calculator.divide(10, 0) - -def test_divide_negative_numbers(calculator): - result = calculator.divide(-10, 2) - assert result == -5.0 - \ No newline at end of file +def test_webhook_post(client): + # Simule un update Telegram minimal + update_json = { + "update_id": 123456789, + "message": { + "message_id": 1, + "from": {"id": 123, "is_bot": False, "first_name": "Test"}, + "chat": {"id": 123, "type": "private"}, + "date": 1680000000, + "text": "Bonjour" + } + } + response = client.post("/webhook", json=update_json) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 2ea8503..1fd70aa 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -1,76 +1,73 @@ import pytest -from unittest.mock import AsyncMock, patch, MagicMock -import os -import datetime +from unittest.mock import AsyncMock, MagicMock, patch +from src.telegram_handler import telegram_handler -# --- Fixtures Pytest pour le Mocking --- +@pytest.mark.asyncio +async def test_start_command_sends_menu(monkeypatch): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.from_user.full_name = "Test User" + mock_update.message.from_user.username = "testuser" + mock_update.message.chat_id = 123 + mock_update.message.text = "/start" -@pytest.fixture(scope="module", autouse=True) -def mock_env_vars(): - # Import ici pour garantir que le patch est effectif avant toute utilisation - from src.config import Settings - mock_settings_instance = MagicMock(spec=Settings) - mock_settings_instance.TELEGRAM_BOT_TOKEN = "mock_telegram_token_for_tests" - mock_settings_instance.MISTRAL_API_KEY = "mock_mistral_api_key_for_tests" - mock_settings_instance.WEBHOOK_URL = "http://mock.webhook.url/webhook_for_tests" - mock_settings_instance.TELEGRAM_API_URL = "https://api.telegram.org" # Ajoutez toutes les vars nécessaires + # Patch send_message + send_message_mock = AsyncMock() + monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - with patch('src.config.get_settings', return_value=mock_settings_instance): - # Ici, en mockant get_settings, on contourne complètement la lecture du .env. - yield + await telegram_handler.start_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 123 + assert "Bienvenue sur le bot KOZ" in kwargs["text"] -@pytest.fixture -def ptb_app(): - # Import après le mock - from src.telegram_handler import ptb_app as real_ptb_app - return real_ptb_app +@pytest.mark.asyncio +async def test_help_command(monkeypatch): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.chat_id = 456 -@pytest.fixture -def dynamodb_repo(): - from src.dynamodb_repository import dynamodb_repo as real_dynamodb_repo - return real_dynamodb_repo + send_message_mock = AsyncMock() + monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) -@pytest.fixture -def mock_update(): - update = AsyncMock() - update.message.reply_text = AsyncMock() - update.message.text = "Hello world" - update.message.chat_id = 12345 - update.message.message_id = 54321 - update.message.from_user.id = 98765 - update.message.from_user.full_name = "Test User" - update.message.from_user.username = "testuser" - return update + await telegram_handler.help_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 456 + assert "Voici les commandes disponibles" in kwargs["text"] -@pytest.fixture -def mock_context(): - context = MagicMock() - return context +@pytest.mark.asyncio +async def test_clear_command(monkeypatch): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.chat_id = 789 -@pytest.fixture(autouse=True) -def mock_telegram_bot_methods(ptb_app): - with patch.object(ptb_app, 'bot', new_callable=AsyncMock) as mock_bot: - mock_bot.send_message = AsyncMock() - mock_bot.set_webhook = AsyncMock() - mock_bot.id = 1234567 - mock_bot.full_name = "MyBotName" - yield mock_bot + send_message_mock = AsyncMock() + monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) -@pytest.fixture(autouse=True) -def mock_mistral_client(): - with patch('src.telegram_handler.Mistral', new_callable=MagicMock) as MockMistral: - mock_instance = MockMistral.return_value - mock_instance.chat = AsyncMock() - mock_response = MagicMock() - mock_response.choices = [MagicMock()] - mock_response.choices[0].message.content = "This is a mocked AI response." - mock_instance.chat.return_value = mock_response - yield mock_instance + await telegram_handler.clear_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 789 + assert "La conversation a été effacée" in kwargs["text"] -@pytest.fixture(autouse=True) -def mock_dynamodb_repository_save_message(dynamodb_repo): +def test_handle_message_called_on_webhook(client, mock_telegram_handler_singleton): """ - Mocke la méthode save_message du dépôt DynamoDB. + Vérifie que process_telegram_update (donc handle_message) est bien appelé lors d'un POST /webhook. """ - with patch.object(dynamodb_repo, 'save_message', new_callable=AsyncMock) as mock_save_message: - yield mock_save_message + update_json = { + "update_id": 987654321, + "message": { + "message_id": 2, + "from": {"id": 456, "is_bot": False, "first_name": "Tester"}, + "chat": {"id": 456, "type": "private"}, + "date": 1680000001, + "text": "Test handle" + } + } + + with patch('src.telegram_handler.telegram_handler.process_telegram_update', new_callable=AsyncMock) as mock_process_update: + response = client.post("/webhook", json=update_json) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + mock_process_update.assert_awaited_once_with(update_json) From 9bdc8fc55eff03619f2f6aabece71e4b27351ce6 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:48:15 +0000 Subject: [PATCH 73/88] Refactor test setup to include TestClient fixture and improve structure of test cases --- tests/test_telegram_handler.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 1fd70aa..840f454 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -1,7 +1,17 @@ +pytest_plugins = ("pytest_asyncio",) + import pytest from unittest.mock import AsyncMock, MagicMock, patch from src.telegram_handler import telegram_handler +from src.main import app +from fastapi.testclient import TestClient + +@pytest.fixture(scope="module") +def client(): + with TestClient(app) as c: + yield c + @pytest.mark.asyncio async def test_start_command_sends_menu(monkeypatch): mock_update = MagicMock() @@ -11,7 +21,6 @@ async def test_start_command_sends_menu(monkeypatch): mock_update.message.chat_id = 123 mock_update.message.text = "/start" - # Patch send_message send_message_mock = AsyncMock() monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) From dba4fd7a5521b32abe0ed0be76c12008402871d5 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 00:55:56 +0000 Subject: [PATCH 74/88] Refactor test setup to include mocking of telegram_handler singleton for improved isolation in tests --- tests/test_telegram_handler.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 840f454..35a10fd 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -1,5 +1,3 @@ -pytest_plugins = ("pytest_asyncio",) - import pytest from unittest.mock import AsyncMock, MagicMock, patch from src.telegram_handler import telegram_handler @@ -12,6 +10,20 @@ def client(): with TestClient(app) as c: yield c +@pytest.fixture(autouse=True) +def mock_telegram_handler_singleton(): + """ + Mocke le singleton telegram_handler pour éviter l'exécution réelle. + """ + mock_handler = MagicMock() + mock_handler.setup_ptb_handlers = AsyncMock(return_value=None) + mock_handler.configure_telegram_webhook = AsyncMock(return_value=None) + mock_handler.process_telegram_update = AsyncMock(return_value=None) + mock_handler.shutdown_ptb = AsyncMock(return_value=None) + + with patch('src.telegram_handler.telegram_handler', mock_handler): + yield + @pytest.mark.asyncio async def test_start_command_sends_menu(monkeypatch): mock_update = MagicMock() From 106b501a55ff7da125469230eaa5f9150c72de21 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:05:46 +0000 Subject: [PATCH 75/88] Refactor test cases to improve structure and enhance mocking of Telegram handler --- requirements.txt | 1 + tests/test_telegram_handler.py | 88 +++++++++++++--------------------- 2 files changed, 34 insertions(+), 55 deletions(-) diff --git a/requirements.txt b/requirements.txt index 990e6c9..0b078d6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ httpx mistralai python-telegram-bot slowapi +pytest-asyncio diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 35a10fd..dfc1e8e 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -1,31 +1,11 @@ +pytest_plugins = ("pytest_asyncio",) + import pytest from unittest.mock import AsyncMock, MagicMock, patch from src.telegram_handler import telegram_handler -from src.main import app -from fastapi.testclient import TestClient - -@pytest.fixture(scope="module") -def client(): - with TestClient(app) as c: - yield c - -@pytest.fixture(autouse=True) -def mock_telegram_handler_singleton(): - """ - Mocke le singleton telegram_handler pour éviter l'exécution réelle. - """ - mock_handler = MagicMock() - mock_handler.setup_ptb_handlers = AsyncMock(return_value=None) - mock_handler.configure_telegram_webhook = AsyncMock(return_value=None) - mock_handler.process_telegram_update = AsyncMock(return_value=None) - mock_handler.shutdown_ptb = AsyncMock(return_value=None) - - with patch('src.telegram_handler.telegram_handler', mock_handler): - yield - @pytest.mark.asyncio -async def test_start_command_sends_menu(monkeypatch): +async def test_start_command_sends_menu(): mock_update = MagicMock() mock_context = MagicMock() mock_update.message.from_user.full_name = "Test User" @@ -33,46 +13,41 @@ async def test_start_command_sends_menu(monkeypatch): mock_update.message.chat_id = 123 mock_update.message.text = "/start" - send_message_mock = AsyncMock() - monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - - await telegram_handler.start_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 123 - assert "Bienvenue sur le bot KOZ" in kwargs["text"] + with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + await telegram_handler.start_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 123 + assert "Bienvenue sur le bot KOZ" in kwargs["text"] @pytest.mark.asyncio -async def test_help_command(monkeypatch): +async def test_help_command(): mock_update = MagicMock() mock_context = MagicMock() mock_update.message.chat_id = 456 - send_message_mock = AsyncMock() - monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - - await telegram_handler.help_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 456 - assert "Voici les commandes disponibles" in kwargs["text"] + with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + await telegram_handler.help_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 456 + assert "Voici les commandes disponibles" in kwargs["text"] @pytest.mark.asyncio -async def test_clear_command(monkeypatch): +async def test_clear_command(): mock_update = MagicMock() mock_context = MagicMock() mock_update.message.chat_id = 789 - send_message_mock = AsyncMock() - monkeypatch.setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) + with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + await telegram_handler.clear_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 789 + assert "La conversation a été effacée" in kwargs["text"] - await telegram_handler.clear_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 789 - assert "La conversation a été effacée" in kwargs["text"] - -def test_handle_message_called_on_webhook(client, mock_telegram_handler_singleton): +@pytest.mark.asyncio +async def test_handle_message_called_on_webhook(client): """ Vérifie que process_telegram_update (donc handle_message) est bien appelé lors d'un POST /webhook. """ @@ -87,8 +62,11 @@ def test_handle_message_called_on_webhook(client, mock_telegram_handler_singleto } } - with patch('src.telegram_handler.telegram_handler.process_telegram_update', new_callable=AsyncMock) as mock_process_update: - response = client.post("/webhook", json=update_json) - assert response.status_code == 200 - assert response.json() == {"status": "ok"} - mock_process_update.assert_awaited_once_with(update_json) + with patch("src.telegram_handler.telegram_handler.process_telegram_update", new_callable=AsyncMock) as mock_process_update: + from src.main import app + from fastapi.testclient import TestClient + with TestClient(app) as test_client: + response = test_client.post("/webhook", json=update_json) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + mock_process_update.assert_awaited_once_with(update_json) From 46a6a5b15d7496640bcaf33770eb7f93db60cf82 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:09:41 +0000 Subject: [PATCH 76/88] Refactor tests to use setattr for mocking send_message and ensure original method restoration --- tests/test_telegram_handler.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index dfc1e8e..5e5385d 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -13,12 +13,18 @@ async def test_start_command_sends_menu(): mock_update.message.chat_id = 123 mock_update.message.text = "/start" - with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + # Patch send_message sur l'instance du bot via setattr + send_message_mock = AsyncMock() + original_send_message = telegram_handler.ptb_app.bot.send_message + setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) + try: await telegram_handler.start_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 123 assert "Bienvenue sur le bot KOZ" in kwargs["text"] + finally: + setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio async def test_help_command(): @@ -26,12 +32,17 @@ async def test_help_command(): mock_context = MagicMock() mock_update.message.chat_id = 456 - with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + send_message_mock = AsyncMock() + original_send_message = telegram_handler.ptb_app.bot.send_message + setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) + try: await telegram_handler.help_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 456 assert "Voici les commandes disponibles" in kwargs["text"] + finally: + setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio async def test_clear_command(): @@ -39,15 +50,20 @@ async def test_clear_command(): mock_context = MagicMock() mock_update.message.chat_id = 789 - with patch.object(telegram_handler.ptb_app.bot, "send_message", new_callable=AsyncMock) as send_message_mock: + send_message_mock = AsyncMock() + original_send_message = telegram_handler.ptb_app.bot.send_message + setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) + try: await telegram_handler.clear_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 789 assert "La conversation a été effacée" in kwargs["text"] + finally: + setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio -async def test_handle_message_called_on_webhook(client): +async def test_handle_message_called_on_webhook(): """ Vérifie que process_telegram_update (donc handle_message) est bien appelé lors d'un POST /webhook. """ From 8618170dbc59dad3e8cff7c57d13d4bf9557750f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:15:14 +0000 Subject: [PATCH 77/88] Refactor tests to simplify mocking of send_message and improve code readability --- tests/test_telegram_handler.py | 30 ++++++------------------------ 1 file changed, 6 insertions(+), 24 deletions(-) diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 5e5385d..91b59e6 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -2,7 +2,6 @@ import pytest from unittest.mock import AsyncMock, MagicMock, patch -from src.telegram_handler import telegram_handler @pytest.mark.asyncio async def test_start_command_sends_menu(): @@ -13,18 +12,13 @@ async def test_start_command_sends_menu(): mock_update.message.chat_id = 123 mock_update.message.text = "/start" - # Patch send_message sur l'instance du bot via setattr - send_message_mock = AsyncMock() - original_send_message = telegram_handler.ptb_app.bot.send_message - setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - try: + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler await telegram_handler.start_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 123 assert "Bienvenue sur le bot KOZ" in kwargs["text"] - finally: - setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio async def test_help_command(): @@ -32,17 +26,13 @@ async def test_help_command(): mock_context = MagicMock() mock_update.message.chat_id = 456 - send_message_mock = AsyncMock() - original_send_message = telegram_handler.ptb_app.bot.send_message - setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - try: + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler await telegram_handler.help_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 456 assert "Voici les commandes disponibles" in kwargs["text"] - finally: - setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio async def test_clear_command(): @@ -50,23 +40,16 @@ async def test_clear_command(): mock_context = MagicMock() mock_update.message.chat_id = 789 - send_message_mock = AsyncMock() - original_send_message = telegram_handler.ptb_app.bot.send_message - setattr(telegram_handler.ptb_app.bot, "send_message", send_message_mock) - try: + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler await telegram_handler.clear_command(mock_update, mock_context) send_message_mock.assert_awaited_once() args, kwargs = send_message_mock.await_args assert kwargs["chat_id"] == 789 assert "La conversation a été effacée" in kwargs["text"] - finally: - setattr(telegram_handler.ptb_app.bot, "send_message", original_send_message) @pytest.mark.asyncio async def test_handle_message_called_on_webhook(): - """ - Vérifie que process_telegram_update (donc handle_message) est bien appelé lors d'un POST /webhook. - """ update_json = { "update_id": 987654321, "message": { @@ -77,7 +60,6 @@ async def test_handle_message_called_on_webhook(): "text": "Test handle" } } - with patch("src.telegram_handler.telegram_handler.process_telegram_update", new_callable=AsyncMock) as mock_process_update: from src.main import app from fastapi.testclient import TestClient From c8440420c78d0c518e447e7662c78513b80d90a0 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:21:30 +0000 Subject: [PATCH 78/88] Refactor tests to improve structure and enhance mocking for Telegram commands --- src/telegram_handler.py | 239 ++++++++++++---------------------------- 1 file changed, 70 insertions(+), 169 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 4bbb922..91b59e6 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,169 +1,70 @@ -from telegram import Update, Bot, ReplyKeyboardMarkup -from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes -from mistralai import Mistral - -from .dynamodb_repository import dynamodb_repo -from .config import env_vars -from .utils import Utils - -class TelegramHandler: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super(TelegramHandler, cls).__new__(cls) - cls._instance._initialized = False - return cls._instance - - def __init__(self): - if self._initialized: - return - self.api_key = env_vars.MISTRAL_API_KEY - self.TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN - self.MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY - self.MISTRAL_MODEL = "mistral-large-latest" - - if not self.TELEGRAM_BOT_TOKEN: - raise ValueError("TELEGRAM_BOT_TOKEN n'est pas défini.") - if not self.MISTRAL_API_KEY: - raise ValueError("MISTRAL_API_KEY n'est pas défini.") - - self.mistral_client = Mistral(api_key=self.MISTRAL_API_KEY) - self.ptb_app = Application.builder().token(self.TELEGRAM_BOT_TOKEN).updater(None).build() - self._initialized = True - # self.setup_ptb_handlers() - - - async def start_command(self, update: Update, context): - try: - user = update.message.from_user - chat_id = update.message.chat_id - user_name = user.full_name or user.username or "N/A" - Utils.log_warning(f"===== Start command Initializing ====== {self.TELEGRAM_BOT_TOKEN}") - - reply_markup = ReplyKeyboardMarkup( - [["/start", "/help", "/clear"]], - resize_keyboard=True - ) - - response_text = ( - f"Bonjour {user_name} !\n" - "Bienvenue sur le bot KOZ.\n" - "Utilisez le menu ci-dessous pour commencer :" - ) - await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) - except Exception as e: - Utils.log_error(f"Start command error ==== {e}") - - async def handle_message(self, update: Update, context): - try: - if update.message and update.message.text: - user = update.message.from_user - user_message = update.message.text - chat_id = update.message.chat_id - message_id = update.message.message_id - user_name = user.full_name or user.username or "N/A" - - Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") - - Utils.log_warning(f"GET MISTRAL RESPONSE ======") - chat_response = self.mistral_client.chat.complete( - model=self.MISTRAL_MODEL, - messages=[ - { - "role": "user", - "content": user_message, - }, - ] - ) - Utils.log_warning(f"AFTER MISTRAL ======") - - if chat_response: - response_text = chat_response.choices[0].message.content - - bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - self.ptb_app.bot.id, - self.ptb_app.bot.username, - response_text, - "bot", - self.MISTRAL_MODEL - ) - Utils.log_warning(f"ANSWER SAVED =======") - except Exception as e: - error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." - await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) - Utils.log_error(f"[handle_message] Erreur: {e}") - - async def help_command(self, update: Update, context): - try: - chat_id = update.message.chat_id - response_text = ( - "Voici les commandes disponibles :\n" - "/start - Afficher le menu principal\n" - "/help - Afficher l'aide\n" - "/clear - Effacer la conversation" - ) - await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - except Exception as e: - Utils.log_error(f"Help command error ==== {e}") - - async def clear_command(self, update: Update, context): - try: - chat_id = update.message.chat_id - response_text = "La conversation a été effacée" - await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - except Exception as e: - Utils.log_error(f"Clear command error ==== {e}") - - async def _error_handler(self, update: object, context: ContextTypes.DEFAULT_TYPE) -> None: - Utils.log_error(f"== Unhandled Telegram exception: {context.error}") - - async def setup_ptb_handlers(self): - try: - self.ptb_app.add_handler(CommandHandler("start", self.start_command)) - self.ptb_app.add_handler(CommandHandler("help", self.help_command)) - self.ptb_app.add_handler(CommandHandler("clear", self.clear_command)) - Utils.log_warning("Handle first message ====") - self.ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)) - self.ptb_app.add_error_handler(self._error_handler) - await self.ptb_app.initialize() - Utils.log_warning("Handlers Telegram initialisés.") - except Exception as e: - Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") - - async def configure_telegram_webhook(self, webhook_url: str): - api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{self.TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" - if not api_webhook_url: - Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") - return - - bot = Bot(self.TELEGRAM_BOT_TOKEN) - try: - current_webhook = await bot.get_webhook_info() - Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") - if current_webhook.url != api_webhook_url: - try: - await bot.set_webhook(url=api_webhook_url) - Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") - except Exception as bot_error: - Utils.log_error(f"Erreur configuration du webhook url : {bot_error}") - else: - Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") - except Exception as e: - Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") - - async def process_telegram_update(self, update_json: dict): - update = Update.de_json(update_json, self.ptb_app.bot) - await self.ptb_app.process_update(update) - - async def shutdown_ptb(self): - await self.ptb_app.shutdown() - Utils.log_warning("Application Telegram arrêtée.") - -# Singleton instance -telegram_handler = TelegramHandler() +pytest_plugins = ("pytest_asyncio",) + +import pytest +from unittest.mock import AsyncMock, MagicMock, patch + +@pytest.mark.asyncio +async def test_start_command_sends_menu(): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.from_user.full_name = "Test User" + mock_update.message.from_user.username = "testuser" + mock_update.message.chat_id = 123 + mock_update.message.text = "/start" + + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler + await telegram_handler.start_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 123 + assert "Bienvenue sur le bot KOZ" in kwargs["text"] + +@pytest.mark.asyncio +async def test_help_command(): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.chat_id = 456 + + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler + await telegram_handler.help_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 456 + assert "Voici les commandes disponibles" in kwargs["text"] + +@pytest.mark.asyncio +async def test_clear_command(): + mock_update = MagicMock() + mock_context = MagicMock() + mock_update.message.chat_id = 789 + + with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: + from src.telegram_handler import telegram_handler + await telegram_handler.clear_command(mock_update, mock_context) + send_message_mock.assert_awaited_once() + args, kwargs = send_message_mock.await_args + assert kwargs["chat_id"] == 789 + assert "La conversation a été effacée" in kwargs["text"] + +@pytest.mark.asyncio +async def test_handle_message_called_on_webhook(): + update_json = { + "update_id": 987654321, + "message": { + "message_id": 2, + "from": {"id": 456, "is_bot": False, "first_name": "Tester"}, + "chat": {"id": 456, "type": "private"}, + "date": 1680000001, + "text": "Test handle" + } + } + with patch("src.telegram_handler.telegram_handler.process_telegram_update", new_callable=AsyncMock) as mock_process_update: + from src.main import app + from fastapi.testclient import TestClient + with TestClient(app) as test_client: + response = test_client.post("/webhook", json=update_json) + assert response.status_code == 200 + assert response.json() == {"status": "ok"} + mock_process_update.assert_awaited_once_with(update_json) From 711349b8239b9f55cc9880d61613ff0da0bed435 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:32:31 +0000 Subject: [PATCH 79/88] Refactor test setup to enhance mocking and improve test isolation for Telegram handler --- requirements.txt | 1 - src/telegram_handler.py | 239 +++++++++++++++++++++++---------- tests/test_telegram_handler.py | 128 +++++++++--------- 3 files changed, 235 insertions(+), 133 deletions(-) diff --git a/requirements.txt b/requirements.txt index 0b078d6..990e6c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,4 +7,3 @@ httpx mistralai python-telegram-bot slowapi -pytest-asyncio diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 91b59e6..4bbb922 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -1,70 +1,169 @@ -pytest_plugins = ("pytest_asyncio",) - -import pytest -from unittest.mock import AsyncMock, MagicMock, patch - -@pytest.mark.asyncio -async def test_start_command_sends_menu(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.from_user.full_name = "Test User" - mock_update.message.from_user.username = "testuser" - mock_update.message.chat_id = 123 - mock_update.message.text = "/start" - - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.start_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 123 - assert "Bienvenue sur le bot KOZ" in kwargs["text"] - -@pytest.mark.asyncio -async def test_help_command(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.chat_id = 456 - - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.help_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 456 - assert "Voici les commandes disponibles" in kwargs["text"] - -@pytest.mark.asyncio -async def test_clear_command(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.chat_id = 789 - - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.clear_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 789 - assert "La conversation a été effacée" in kwargs["text"] - -@pytest.mark.asyncio -async def test_handle_message_called_on_webhook(): - update_json = { - "update_id": 987654321, - "message": { - "message_id": 2, - "from": {"id": 456, "is_bot": False, "first_name": "Tester"}, - "chat": {"id": 456, "type": "private"}, - "date": 1680000001, - "text": "Test handle" - } - } - with patch("src.telegram_handler.telegram_handler.process_telegram_update", new_callable=AsyncMock) as mock_process_update: - from src.main import app - from fastapi.testclient import TestClient - with TestClient(app) as test_client: - response = test_client.post("/webhook", json=update_json) - assert response.status_code == 200 - assert response.json() == {"status": "ok"} - mock_process_update.assert_awaited_once_with(update_json) +from telegram import Update, Bot, ReplyKeyboardMarkup +from telegram.ext import Application, MessageHandler, filters, CommandHandler, ContextTypes +from mistralai import Mistral + +from .dynamodb_repository import dynamodb_repo +from .config import env_vars +from .utils import Utils + +class TelegramHandler: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(TelegramHandler, cls).__new__(cls) + cls._instance._initialized = False + return cls._instance + + def __init__(self): + if self._initialized: + return + self.api_key = env_vars.MISTRAL_API_KEY + self.TELEGRAM_BOT_TOKEN = env_vars.TELEGRAM_BOT_TOKEN + self.MISTRAL_API_KEY = env_vars.MISTRAL_API_KEY + self.MISTRAL_MODEL = "mistral-large-latest" + + if not self.TELEGRAM_BOT_TOKEN: + raise ValueError("TELEGRAM_BOT_TOKEN n'est pas défini.") + if not self.MISTRAL_API_KEY: + raise ValueError("MISTRAL_API_KEY n'est pas défini.") + + self.mistral_client = Mistral(api_key=self.MISTRAL_API_KEY) + self.ptb_app = Application.builder().token(self.TELEGRAM_BOT_TOKEN).updater(None).build() + self._initialized = True + # self.setup_ptb_handlers() + + + async def start_command(self, update: Update, context): + try: + user = update.message.from_user + chat_id = update.message.chat_id + user_name = user.full_name or user.username or "N/A" + Utils.log_warning(f"===== Start command Initializing ====== {self.TELEGRAM_BOT_TOKEN}") + + reply_markup = ReplyKeyboardMarkup( + [["/start", "/help", "/clear"]], + resize_keyboard=True + ) + + response_text = ( + f"Bonjour {user_name} !\n" + "Bienvenue sur le bot KOZ.\n" + "Utilisez le menu ci-dessous pour commencer :" + ) + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text, reply_markup=reply_markup) + except Exception as e: + Utils.log_error(f"Start command error ==== {e}") + + async def handle_message(self, update: Update, context): + try: + if update.message and update.message.text: + user = update.message.from_user + user_message = update.message.text + chat_id = update.message.chat_id + message_id = update.message.message_id + user_name = user.full_name or user.username or "N/A" + + Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + + Utils.log_warning(f"GET MISTRAL RESPONSE ======") + chat_response = self.mistral_client.chat.complete( + model=self.MISTRAL_MODEL, + messages=[ + { + "role": "user", + "content": user_message, + }, + ] + ) + Utils.log_warning(f"AFTER MISTRAL ======") + + if chat_response: + response_text = chat_response.choices[0].message.content + + bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + self.ptb_app.bot.id, + self.ptb_app.bot.username, + response_text, + "bot", + self.MISTRAL_MODEL + ) + Utils.log_warning(f"ANSWER SAVED =======") + except Exception as e: + error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." + await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) + Utils.log_error(f"[handle_message] Erreur: {e}") + + async def help_command(self, update: Update, context): + try: + chat_id = update.message.chat_id + response_text = ( + "Voici les commandes disponibles :\n" + "/start - Afficher le menu principal\n" + "/help - Afficher l'aide\n" + "/clear - Effacer la conversation" + ) + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Help command error ==== {e}") + + async def clear_command(self, update: Update, context): + try: + chat_id = update.message.chat_id + response_text = "La conversation a été effacée" + await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) + except Exception as e: + Utils.log_error(f"Clear command error ==== {e}") + + async def _error_handler(self, update: object, context: ContextTypes.DEFAULT_TYPE) -> None: + Utils.log_error(f"== Unhandled Telegram exception: {context.error}") + + async def setup_ptb_handlers(self): + try: + self.ptb_app.add_handler(CommandHandler("start", self.start_command)) + self.ptb_app.add_handler(CommandHandler("help", self.help_command)) + self.ptb_app.add_handler(CommandHandler("clear", self.clear_command)) + Utils.log_warning("Handle first message ====") + self.ptb_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, self.handle_message)) + self.ptb_app.add_error_handler(self._error_handler) + await self.ptb_app.initialize() + Utils.log_warning("Handlers Telegram initialisés.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration des handlers Telegram : {e}") + + async def configure_telegram_webhook(self, webhook_url: str): + api_webhook_url = f"{env_vars.TELEGRAM_API_URL}{self.TELEGRAM_BOT_TOKEN}/setWebhook?url={webhook_url}" + if not api_webhook_url: + Utils.log_error("WEBHOOK_URL non défini. Le webhook ne sera pas configuré automatiquement.") + return + + bot = Bot(self.TELEGRAM_BOT_TOKEN) + try: + current_webhook = await bot.get_webhook_info() + Utils.log_warning(f"=== TELEGRAM to connect : \n NEW : {webhook_url} \n OLD: {current_webhook.url}") + if current_webhook.url != api_webhook_url: + try: + await bot.set_webhook(url=api_webhook_url) + Utils.log_warning(f"Webhook Telegram configuré sur : {webhook_url}") + except Exception as bot_error: + Utils.log_error(f"Erreur configuration du webhook url : {bot_error}") + else: + Utils.log_warning("Webhook déjà configuré, aucune modification nécessaire.") + except Exception as e: + Utils.log_error(f"Erreur lors de la configuration du webhook Telegram : {webhook_url} {e}") + + async def process_telegram_update(self, update_json: dict): + update = Update.de_json(update_json, self.ptb_app.bot) + await self.ptb_app.process_update(update) + + async def shutdown_ptb(self): + await self.ptb_app.shutdown() + Utils.log_warning("Application Telegram arrêtée.") + +# Singleton instance +telegram_handler = TelegramHandler() diff --git a/tests/test_telegram_handler.py b/tests/test_telegram_handler.py index 91b59e6..6fa67c7 100644 --- a/tests/test_telegram_handler.py +++ b/tests/test_telegram_handler.py @@ -1,70 +1,74 @@ -pytest_plugins = ("pytest_asyncio",) - import pytest -from unittest.mock import AsyncMock, MagicMock, patch +from unittest.mock import AsyncMock, patch, MagicMock + +# --- Fixtures Pytest pour le Mocking --- + +@pytest.fixture(scope="module", autouse=True) +def test_mock_env_vars(): + # Import ici pour garantir que le patch est effectif avant toute utilisation + from src.config import Settings + mock_settings_instance = MagicMock(spec=Settings) + mock_settings_instance.TELEGRAM_BOT_TOKEN = "mock_telegram_token_for_tests" + mock_settings_instance.MISTRAL_API_KEY = "mock_mistral_api_key_for_tests" + mock_settings_instance.WEBHOOK_URL = "http://mock.webhook.url/webhook_for_tests" + mock_settings_instance.TELEGRAM_API_URL = "https://api.telegram.org" # Ajoutez toutes les vars nécessaires + + with patch('src.config.get_settings', return_value=mock_settings_instance): + # Ici, en mockant get_settings, on contourne complètement la lecture du .env. + yield -@pytest.mark.asyncio -async def test_start_command_sends_menu(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.from_user.full_name = "Test User" - mock_update.message.from_user.username = "testuser" - mock_update.message.chat_id = 123 - mock_update.message.text = "/start" +@pytest.fixture +def ptb_app(): + # Import après le mock + from src.telegram_handler import ptb_app as real_ptb_app + return real_ptb_app - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.start_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 123 - assert "Bienvenue sur le bot KOZ" in kwargs["text"] +@pytest.fixture +def dynamodb_repo(): + from src.dynamodb_repository import dynamodb_repo as real_dynamodb_repo + return real_dynamodb_repo -@pytest.mark.asyncio -async def test_help_command(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.chat_id = 456 +@pytest.fixture +def mock_update(): + update = AsyncMock() + update.message.reply_text = AsyncMock() + update.message.text = "Hello world" + update.message.chat_id = 12345 + update.message.message_id = 54321 + update.message.from_user.id = 98765 + update.message.from_user.full_name = "Test User" + update.message.from_user.username = "testuser" + return update - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.help_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 456 - assert "Voici les commandes disponibles" in kwargs["text"] +@pytest.fixture +def mock_context(): + context = MagicMock() + return context -@pytest.mark.asyncio -async def test_clear_command(): - mock_update = MagicMock() - mock_context = MagicMock() - mock_update.message.chat_id = 789 +@pytest.fixture(autouse=True) +def mock_telegram_bot_methods(ptb_app): + with patch.object(ptb_app, 'bot', new_callable=AsyncMock) as mock_bot: + mock_bot.send_message = AsyncMock() + mock_bot.set_webhook = AsyncMock() + mock_bot.id = 1234567 + mock_bot.full_name = "MyBotName" + yield mock_bot - with patch("src.telegram_handler.telegram_handler.ptb_app.bot.send_message", new_callable=AsyncMock) as send_message_mock: - from src.telegram_handler import telegram_handler - await telegram_handler.clear_command(mock_update, mock_context) - send_message_mock.assert_awaited_once() - args, kwargs = send_message_mock.await_args - assert kwargs["chat_id"] == 789 - assert "La conversation a été effacée" in kwargs["text"] +@pytest.fixture(autouse=True) +def mock_mistral_client(): + with patch('src.telegram_handler.Mistral', new_callable=MagicMock) as MockMistral: + mock_instance = MockMistral.return_value + mock_instance.chat = AsyncMock() + mock_response = MagicMock() + mock_response.choices = [MagicMock()] + mock_response.choices[0].message.content = "This is a mocked AI response." + mock_instance.chat.return_value = mock_response + yield mock_instance -@pytest.mark.asyncio -async def test_handle_message_called_on_webhook(): - update_json = { - "update_id": 987654321, - "message": { - "message_id": 2, - "from": {"id": 456, "is_bot": False, "first_name": "Tester"}, - "chat": {"id": 456, "type": "private"}, - "date": 1680000001, - "text": "Test handle" - } - } - with patch("src.telegram_handler.telegram_handler.process_telegram_update", new_callable=AsyncMock) as mock_process_update: - from src.main import app - from fastapi.testclient import TestClient - with TestClient(app) as test_client: - response = test_client.post("/webhook", json=update_json) - assert response.status_code == 200 - assert response.json() == {"status": "ok"} - mock_process_update.assert_awaited_once_with(update_json) +@pytest.fixture(autouse=True) +def mock_dynamodb_repository_save_message(dynamodb_repo): + """ + Mocke la méthode save_message du dépôt DynamoDB. + """ + with patch.object(dynamodb_repo, 'save_message', new_callable=AsyncMock) as mock_save_message: + yield mock_save_message From 940218b29143bf72b4f9eab30b7f7a7da13d5596 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Thu, 5 Jun 2025 01:50:58 +0000 Subject: [PATCH 80/88] Refactor TelegramHandler to comment out message saving logic and remove unnecessary logging --- src/telegram_handler.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 4bbb922..eb38c82 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -39,7 +39,6 @@ async def start_command(self, update: Update, context): user = update.message.from_user chat_id = update.message.chat_id user_name = user.full_name or user.username or "N/A" - Utils.log_warning(f"===== Start command Initializing ====== {self.TELEGRAM_BOT_TOKEN}") reply_markup = ReplyKeyboardMarkup( [["/start", "/help", "/clear"]], @@ -65,7 +64,7 @@ async def handle_message(self, update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"GET MISTRAL RESPONSE ======") chat_response = self.mistral_client.chat.complete( @@ -84,15 +83,15 @@ async def handle_message(self, update: Update, context): bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - self.ptb_app.bot.id, - self.ptb_app.bot.username, - response_text, - "bot", - self.MISTRAL_MODEL - ) + # await dynamodb_repo.save_message( + # chat_id, + # bot_message.message_id, + # self.ptb_app.bot.id, + # self.ptb_app.bot.username, + # response_text, + # "bot", + # self.MISTRAL_MODEL + # ) Utils.log_warning(f"ANSWER SAVED =======") except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." From 2223c4f79a7bf265aacb2ccba65aea46f878a51b Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Fri, 6 Jun 2025 16:49:44 +0000 Subject: [PATCH 81/88] Fix error handling in save_message to raise exceptions instead of returning silently --- src/dynamodb_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 484e512..00a7ed2 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -63,7 +63,7 @@ async def save_message( except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}") # L'erreur n'est pas levée pour ne pas interrompre le flux du bot - return + raise e async def get_chat_history(self, chat_id: int, limit: int = 100) -> list[dict]: """ From f0eba18ab07d0f76c9fced31ba6a44b94d6eeef7 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sat, 7 Jun 2025 11:44:08 +0000 Subject: [PATCH 82/88] Refactor DynamoDB message saving logic to use attribute value format and improve error handling in TelegramHandler --- src/dynamodb_repository.py | 24 +++++++++++++----------- src/telegram_handler.py | 22 ++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 00a7ed2..3d1f1fe 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -45,21 +45,23 @@ async def save_message( try: id = str(uuid.uuid4()) # Génère un UUID item = { - 'id': id, - 'chat_id': str(chat_id), - 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), - 'message_id': str(message_id), - 'user_id': str(user_id), - 'user_name': user_name, - 'text': text, - 'role': role, + "id": {"S": str(id)}, + "chat_id": {"S": str(chat_id)}, + "timestamp": {"S": datetime.datetime.now(datetime.timezone.utc).isoformat()}, + "message_id": {"S": str(message_id)}, + "user_id": {"S": str(user_id)}, + "user_name": {"S": user_name}, + "text": {"S": text}, + "role": {"S": role}, } if ai_model: - item['ai_model'] = ai_model + item["ai_model"] = {"S": ai_model} + + Utils.insert_data(item) # Exécute l'opération put_item (synchrone) dans un thread séparé - return await asyncio.to_thread(self.table.put_item, Item=item) - # Utils.log_info(f"Message enregistré dans DynamoDB: chat_id={chat_id}, role={role}") + # return await asyncio.to_thread(self.table.put_item, Item=item) + return True except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}") # L'erreur n'est pas levée pour ne pas interrompre le flux du bot diff --git a/src/telegram_handler.py b/src/telegram_handler.py index eb38c82..4eadc64 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -64,7 +64,7 @@ async def handle_message(self, update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"GET MISTRAL RESPONSE ======") chat_response = self.mistral_client.chat.complete( @@ -76,23 +76,21 @@ async def handle_message(self, update: Update, context): }, ] ) - Utils.log_warning(f"AFTER MISTRAL ======") if chat_response: response_text = chat_response.choices[0].message.content bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # await dynamodb_repo.save_message( - # chat_id, - # bot_message.message_id, - # self.ptb_app.bot.id, - # self.ptb_app.bot.username, - # response_text, - # "bot", - # self.MISTRAL_MODEL - # ) - Utils.log_warning(f"ANSWER SAVED =======") + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + self.ptb_app.bot.id, + self.ptb_app.bot.username, + response_text, + "bot", + self.MISTRAL_MODEL + ) except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) From 26ee937acafe291a71845491f827f5789b56d5e4 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sat, 7 Jun 2025 12:18:42 +0000 Subject: [PATCH 83/88] Comment out message saving logic in handle_message and chat completion to disable database interactions temporarily --- src/telegram_handler.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/telegram_handler.py b/src/telegram_handler.py index 4eadc64..a33881e 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -64,7 +64,7 @@ async def handle_message(self, update: Update, context): user_name = user.full_name or user.username or "N/A" Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"GET MISTRAL RESPONSE ======") chat_response = self.mistral_client.chat.complete( @@ -82,15 +82,15 @@ async def handle_message(self, update: Update, context): bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - await dynamodb_repo.save_message( - chat_id, - bot_message.message_id, - self.ptb_app.bot.id, - self.ptb_app.bot.username, - response_text, - "bot", - self.MISTRAL_MODEL - ) + # await dynamodb_repo.save_message( + # chat_id, + # bot_message.message_id, + # self.ptb_app.bot.id, + # self.ptb_app.bot.username, + # response_text, + # "bot", + # self.MISTRAL_MODEL + # ) except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) From 6bb8fa3151e843c493940c7ed3c234e5fe9a0b62 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 8 Jun 2025 13:39:43 +0000 Subject: [PATCH 84/88] Refactor DynamoDB interactions in Utils and TelegramHandler; implement chat history retrieval methods and enhance error handling. --- src/dynamodb_repository.py | 88 +++++++++++++++++++------------------- src/telegram_handler.py | 23 +++++----- src/utils.py | 71 +++++++++++++++++++++++++++--- 3 files changed, 123 insertions(+), 59 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 3d1f1fe..08e986c 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -17,17 +17,17 @@ def __init__(self): self.region_name = env_vars.AWS_REGION_NAME self._table = None # Sera initialisé lors du premier accès - @property - def table(self): - """ - Initialise la table DynamoDB si elle n'est pas déjà initialisée. - Utilise un singleton-like pour la table. - """ - if self._table is None: - Utils.log_info(f"Initialisation de la connexion DynamoDB à la table: {self.table_name} en région: {self.region_name}") - dynamodb = boto3.resource('dynamodb', region_name=self.region_name) - self._table = dynamodb.Table(self.table_name) - return self._table + # @property + # def table(self): + # """ + # Initialise la table DynamoDB si elle n'est pas déjà initialisée. + # Utilise un singleton-like pour la table. + # """ + # if self._table is None: + # Utils.log_info(f"Initialisation de la connexion DynamoDB à la table: {self.table_name} en région: {self.region_name}") + # dynamodb = boto3.resource('dynamodb', region_name=self.region_name) + # self._table = dynamodb.Table(self.table_name) + # return self._table async def save_message( self, @@ -73,18 +73,19 @@ async def get_chat_history(self, chat_id: int, limit: int = 100) -> list[dict]: Retourne une liste de dictionnaires représentant les messages. """ try: - response = await asyncio.to_thread( - self.table.query, - KeyConditionExpression=Key('chat_id').eq(str(chat_id)), - Limit=limit, - ScanIndexForward=True # True pour tri ascendant (du plus ancien au plus récent) - ) - Utils.log_info(f"Historique du chat {chat_id} récupéré. Messages trouvés: {len(response.get('Items', []))}") - return response.get('Items', []) - except ClientError as e: - error_code = e.response['Error']['Code'] - Utils.log_error(f"Erreur DynamoDB lors de la récupération de l'historique: {error_code} - {e}") - return [] + return Utils.get_chat_history(chat_id=chat_id, limit=limit) + # response = await asyncio.to_thread( + # self.table.query, + # KeyConditionExpression=Key('chat_id').eq(str(chat_id)), + # Limit=limit, + # ScanIndexForward=True # True pour tri ascendant (du plus ancien au plus récent) + # ) + # Utils.log_info(f"Historique du chat {chat_id} récupéré. Messages trouvés: {len(response.get('Items', []))}") + # # return response.get('Items', []) + # except ClientError as e: + # error_code = e.response['Error']['Code'] + # Utils.log_error(f"Erreur DynamoDB lors de la récupération de l'historique: {error_code} - {e}") + # return [] except Exception as e: Utils.log_error(f"Erreur inattendue lors de la récupération de l'historique: {e}") return [] @@ -95,31 +96,32 @@ async def get_user_messages_by_date_range(self, user_id: int, start_timestamp: s dans une plage de dates donnée, en utilisant le GSI 'UserIndex' (cf fichier dynamo.bash). Les timestamps doivent être au format ISO 8601 (ex: "2025-01-01T00:00:00Z"). """ - gsi_name = "UserIndex" + # gsi_name = "UserIndex" try: - query_params = { - 'IndexName': gsi_name, - 'KeyConditionExpression': Key('user_id').eq(str(user_id)) & Key('timestamp').between(start_timestamp, end_timestamp), - 'Limit': limit, - 'ScanIndexForward': True # Du plus ancien au plus récent - } + return Utils.get_user_chats(user_id, start_timestamp, end_timestamp, limit) + # query_params = { + # 'IndexName': gsi_name, + # 'KeyConditionExpression': Key('user_id').eq(str(user_id)) & Key('timestamp').between(start_timestamp, end_timestamp), + # 'Limit': limit, + # 'ScanIndexForward': True # Du plus ancien au plus récent + # } - items = [] - response = await asyncio.to_thread(self.table.query, **query_params) - items.extend(response.get('Items', [])) + # items = [] + # response = await asyncio.to_thread(self.table.query, **query_params) + # items.extend(response.get('Items', [])) - while 'LastEvaluatedKey' in response: - query_params['ExclusiveStartKey'] = response['LastEvaluatedKey'] - response = await asyncio.to_thread(self.table.query, **query_params) - items.extend(response.get('Items', [])) + # while 'LastEvaluatedKey' in response: + # query_params['ExclusiveStartKey'] = response['LastEvaluatedKey'] + # response = await asyncio.to_thread(self.table.query, **query_params) + # items.extend(response.get('Items', [])) - Utils.log_info(f"Messages pour l'utilisateur {user_id} dans la plage {start_timestamp} à {end_timestamp} récupérés. Messages trouvés: {len(items)}") - return items + # Utils.log_info(f"Messages pour l'utilisateur {user_id} dans la plage {start_timestamp} à {end_timestamp} récupérés. Messages trouvés: {len(items)}") + # return items - except ClientError as e: - error_code = e.response['Error']['Code'] - Utils.log_error(f"Erreur DynamoDB lors de la récupération des messages de l'utilisateur par plage de date: {error_code} - {e}") - return [] + # except ClientError as e: + # error_code = e.response['Error']['Code'] + # Utils.log_error(f"Erreur DynamoDB lors de la récupération des messages de l'utilisateur par plage de date: {error_code} - {e}") + # return [] except Exception as e: Utils.log_error(f"Erreur inattendue lors de la récupération des messages de l'utilisateur par plage de date: {e}") return [] diff --git a/src/telegram_handler.py b/src/telegram_handler.py index a33881e..ca4532e 100644 --- a/src/telegram_handler.py +++ b/src/telegram_handler.py @@ -63,8 +63,8 @@ async def handle_message(self, update: Update, context): message_id = update.message.message_id user_name = user.full_name or user.username or "N/A" - Utils.log_warning(f"KOZ_MSG ======= {user_name} - {user_message}") - # await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") + Utils.log_warning(f"KOZ_MSG ======== {user_name} - {user_message}") + await dynamodb_repo.save_message(chat_id, message_id, user.id, user_name, user_message, "user") Utils.log_warning(f"GET MISTRAL RESPONSE ======") chat_response = self.mistral_client.chat.complete( @@ -76,21 +76,22 @@ async def handle_message(self, update: Update, context): }, ] ) + Utils.log_warning(f"MISTRAL RESPONDED ====== ") if chat_response: response_text = chat_response.choices[0].message.content bot_message = await self.ptb_app.bot.send_message(chat_id=chat_id, text=response_text) - # await dynamodb_repo.save_message( - # chat_id, - # bot_message.message_id, - # self.ptb_app.bot.id, - # self.ptb_app.bot.username, - # response_text, - # "bot", - # self.MISTRAL_MODEL - # ) + await dynamodb_repo.save_message( + chat_id, + bot_message.message_id, + self.ptb_app.bot.id, + self.ptb_app.bot.username, + response_text, + "bot", + self.MISTRAL_MODEL + ) except Exception as e: error_response = "Désolé, une erreur est survenue lors du traitement de votre demande." await self.ptb_app.bot.send_message(chat_id=chat_id, text=error_response) diff --git a/src/utils.py b/src/utils.py index b22ebc2..06dc4fa 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,7 @@ import json import logging from typing import List +from boto3.dynamodb.conditions import Key, Attr import boto3 @@ -62,11 +63,71 @@ def get_session(): return boto3.Session( region_name=env_vars.AWS_REGION_NAME, env_name=env_vars.ENV_NAME ) + + @staticmethod + def get_dynamo_resource() -> boto3.resource: + # In Lambda, use the role credentials + Utils.log_info(f"Initialisation de la connexion DynamoDB à la table: {env_vars.DYNAMO_TABLE} en région: {env_vars.AWS_REGION_NAME}") + return boto3.resource("dynamodb", region_name=env_vars.AWS_REGION_NAME) + + @staticmethod def insert_data(item): - dynamo_client = boto3.client("dynamodb", region_name=env_vars.AWS_REGION_NAME) - dynamo_client.put_item( - TableName=env_vars.DYNAMO_TABLE, - Item=item, - ) + try: + dynamo_client = boto3.client("dynamodb", region_name=env_vars.AWS_REGION_NAME) + dynamo_client.put_item( + TableName=env_vars.DYNAMO_TABLE, + Item=item, + ) + return True + except Exception as e: + logger.error(msg=f"Erreur lors de l'insertion dans DynamoDB: {str(e)}") + raise e + + @staticmethod + def get_chat_history(chat_id: str, limit: int = 100) -> list[dict]: + try: + dynamo_resource = Utils.get_dynamo_resource() + table = dynamo_resource.Table(env_vars.DYNAMO_TABLE) + + response = table.query( + KeyConditionExpression=Key('chat_id').eq(str(chat_id)), + Limit=limit, + ScanIndexForward=True, # Trier par timestamp croissant + ) + + items: list[dict] = response.get("Items", []) + logger.info(msg=f"Historique du chat {chat_id} récupéré. Messages trouvés: {len(items)}") + return items + except Exception as e: + logger.error(msg=f"Erreur : chat history in DynamoDB: {str(e)}") + raise e + + @staticmethod + def get_user_chats(user_id: str, start_timestamp: str, end_timestamp: str, limit: int = 100) -> list[dict]: + try: + dynamo_resource = Utils.get_dynamo_resource() + table = dynamo_resource.Table(env_vars.DYNAMO_TABLE) + + # Utilise une requête sur l'index avec le user_id + query_params = { + 'IndexName': "UserIndex", + 'KeyConditionExpression': Key('user_id').eq(str(user_id)) & Key('timestamp').between(start_timestamp, end_timestamp), + 'Limit': limit, + 'ScanIndexForward': False # Du plus récents au plus anciens + } + + items = [] + response = table.query(**query_params) + items.extend(response.get('Items', [])) + + while 'LastEvaluatedKey' in response: + query_params['ExclusiveStartKey'] = response['LastEvaluatedKey'] + response = table.query(**query_params) + items.extend(response.get('Items', [])) + + return items + except Exception as e: + logger.error(msg=f"Erreur : chat history in DynamoDB: {str(e)}") + raise e From ac0482883aa578de4500309ba3b623c69c124c0f Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 8 Jun 2025 14:06:21 +0000 Subject: [PATCH 85/88] Refactor DynamoDBRepository to restore table initialization logic and enhance message retrieval methods; improve error handling. --- src/dynamodb_repository.py | 94 +++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 47 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 08e986c..e60621d 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -17,17 +17,17 @@ def __init__(self): self.region_name = env_vars.AWS_REGION_NAME self._table = None # Sera initialisé lors du premier accès - # @property - # def table(self): - # """ - # Initialise la table DynamoDB si elle n'est pas déjà initialisée. - # Utilise un singleton-like pour la table. - # """ - # if self._table is None: - # Utils.log_info(f"Initialisation de la connexion DynamoDB à la table: {self.table_name} en région: {self.region_name}") - # dynamodb = boto3.resource('dynamodb', region_name=self.region_name) - # self._table = dynamodb.Table(self.table_name) - # return self._table + @property + def table(self): + """ + Initialise la table DynamoDB si elle n'est pas déjà initialisée. + Utilise un singleton-like pour la table. + """ + if self._table is None: + Utils.log_info(f"Initialisation de la connexion DynamoDB à la table: {self.table_name} en région: {self.region_name}") + dynamodb = boto3.resource('dynamodb', region_name=self.region_name) + self._table = dynamodb.Table(self.table_name) + return self._table async def save_message( self, @@ -57,10 +57,10 @@ async def save_message( if ai_model: item["ai_model"] = {"S": ai_model} - Utils.insert_data(item) + # Utils.insert_data(item) # Exécute l'opération put_item (synchrone) dans un thread séparé - # return await asyncio.to_thread(self.table.put_item, Item=item) + # await asyncio.to_thread(self.table.put_item, Item=item) return True except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}") @@ -73,19 +73,19 @@ async def get_chat_history(self, chat_id: int, limit: int = 100) -> list[dict]: Retourne une liste de dictionnaires représentant les messages. """ try: - return Utils.get_chat_history(chat_id=chat_id, limit=limit) - # response = await asyncio.to_thread( - # self.table.query, - # KeyConditionExpression=Key('chat_id').eq(str(chat_id)), - # Limit=limit, - # ScanIndexForward=True # True pour tri ascendant (du plus ancien au plus récent) - # ) - # Utils.log_info(f"Historique du chat {chat_id} récupéré. Messages trouvés: {len(response.get('Items', []))}") - # # return response.get('Items', []) - # except ClientError as e: - # error_code = e.response['Error']['Code'] - # Utils.log_error(f"Erreur DynamoDB lors de la récupération de l'historique: {error_code} - {e}") - # return [] + # return Utils.get_chat_history(chat_id=chat_id, limit=limit) + response = await asyncio.to_thread( + self.table.query, + KeyConditionExpression=Key('chat_id').eq(str(chat_id)), + Limit=limit, + ScanIndexForward=True # True pour tri ascendant (du plus ancien au plus récent) + ) + Utils.log_info(f"Historique du chat {chat_id} récupéré. Messages trouvés: {len(response.get('Items', []))}") + # return response.get('Items', []) + except ClientError as e: + error_code = e.response['Error']['Code'] + Utils.log_error(f"Erreur DynamoDB lors de la récupération de l'historique: {error_code} - {e}") + return [] except Exception as e: Utils.log_error(f"Erreur inattendue lors de la récupération de l'historique: {e}") return [] @@ -96,32 +96,32 @@ async def get_user_messages_by_date_range(self, user_id: int, start_timestamp: s dans une plage de dates donnée, en utilisant le GSI 'UserIndex' (cf fichier dynamo.bash). Les timestamps doivent être au format ISO 8601 (ex: "2025-01-01T00:00:00Z"). """ - # gsi_name = "UserIndex" + gsi_name = "UserIndex" try: - return Utils.get_user_chats(user_id, start_timestamp, end_timestamp, limit) - # query_params = { - # 'IndexName': gsi_name, - # 'KeyConditionExpression': Key('user_id').eq(str(user_id)) & Key('timestamp').between(start_timestamp, end_timestamp), - # 'Limit': limit, - # 'ScanIndexForward': True # Du plus ancien au plus récent - # } + # return Utils.get_user_chats(user_id, start_timestamp, end_timestamp, limit) + query_params = { + 'IndexName': gsi_name, + 'KeyConditionExpression': Key('user_id').eq(str(user_id)) & Key('timestamp').between(start_timestamp, end_timestamp), + 'Limit': limit, + 'ScanIndexForward': True # Du plus ancien au plus récent + } - # items = [] - # response = await asyncio.to_thread(self.table.query, **query_params) - # items.extend(response.get('Items', [])) + items = [] + response = await asyncio.to_thread(self.table.query, **query_params) + items.extend(response.get('Items', [])) - # while 'LastEvaluatedKey' in response: - # query_params['ExclusiveStartKey'] = response['LastEvaluatedKey'] - # response = await asyncio.to_thread(self.table.query, **query_params) - # items.extend(response.get('Items', [])) + while 'LastEvaluatedKey' in response: + query_params['ExclusiveStartKey'] = response['LastEvaluatedKey'] + response = await asyncio.to_thread(self.table.query, **query_params) + items.extend(response.get('Items', [])) - # Utils.log_info(f"Messages pour l'utilisateur {user_id} dans la plage {start_timestamp} à {end_timestamp} récupérés. Messages trouvés: {len(items)}") - # return items + Utils.log_info(f"Messages pour l'utilisateur {user_id} dans la plage {start_timestamp} à {end_timestamp} récupérés. Messages trouvés: {len(items)}") + return items - # except ClientError as e: - # error_code = e.response['Error']['Code'] - # Utils.log_error(f"Erreur DynamoDB lors de la récupération des messages de l'utilisateur par plage de date: {error_code} - {e}") - # return [] + except ClientError as e: + error_code = e.response['Error']['Code'] + Utils.log_error(f"Erreur DynamoDB lors de la récupération des messages de l'utilisateur par plage de date: {error_code} - {e}") + return [] except Exception as e: Utils.log_error(f"Erreur inattendue lors de la récupération des messages de l'utilisateur par plage de date: {e}") return [] From 302de1d9e1dfc73c4f19d797dc2264f9908c604e Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 8 Jun 2025 14:23:35 +0000 Subject: [PATCH 86/88] Add API Gateway permissions for POST and GET methods; enable DynamoDB interactions in the template --- infrastructure/template.yaml | 2 ++ src/dynamodb_repository.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/infrastructure/template.yaml b/infrastructure/template.yaml index 4ff7c7e..8cbedd1 100644 --- a/infrastructure/template.yaml +++ b/infrastructure/template.yaml @@ -122,6 +122,8 @@ Resources: - dynamodb:BatchGetItem - dynamodb:DescribeTable - dynamodb:ConditionCheckItem + - apigateway:POST + - apigateway:GET Resource: "*" Events: Api: diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index e60621d..54cd68a 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -60,7 +60,7 @@ async def save_message( # Utils.insert_data(item) # Exécute l'opération put_item (synchrone) dans un thread séparé - # await asyncio.to_thread(self.table.put_item, Item=item) + await asyncio.to_thread(self.table.put_item, Item=item) return True except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}") From 28614fc38b16f44639c80e01a9938cb54121cc10 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 8 Jun 2025 16:35:48 +0000 Subject: [PATCH 87/88] Refactor save_message method to use simplified item structure and improve UUID handling; comment out unused item creation logic. --- src/dynamodb_repository.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 54cd68a..180245c 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -44,18 +44,29 @@ async def save_message( """ try: id = str(uuid.uuid4()) # Génère un UUID + # item = { + # "id": {"S": str(id)}, + # "chat_id": {"S": str(chat_id)}, + # "timestamp": {"S": datetime.datetime.now(datetime.timezone.utc).isoformat()}, + # "message_id": {"S": str(message_id)}, + # "user_id": {"S": str(user_id)}, + # "user_name": {"S": user_name}, + # "text": {"S": text}, + # "role": {"S": role}, + # } item = { - "id": {"S": str(id)}, - "chat_id": {"S": str(chat_id)}, - "timestamp": {"S": datetime.datetime.now(datetime.timezone.utc).isoformat()}, - "message_id": {"S": str(message_id)}, - "user_id": {"S": str(user_id)}, - "user_name": {"S": user_name}, - "text": {"S": text}, - "role": {"S": role}, + 'id': id, + 'chat_id': str(chat_id), + 'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(), + 'message_id': str(message_id), + 'user_id': str(user_id), + 'user_name': user_name, + 'text': text, + 'role': role, } if ai_model: - item["ai_model"] = {"S": ai_model} + item["ai_model"] = str(ai_model) + # item["ai_model"] = {"S": ai_model} # Utils.insert_data(item) From babba81f172810ee9fa87450852f1bc97e088a95 Mon Sep 17 00:00:00 2001 From: mbradiouf14 Date: Sun, 8 Jun 2025 20:04:30 +0000 Subject: [PATCH 88/88] Comment out the asynchronous put_item operation in save_message to temporarily disable database interactions --- src/dynamodb_repository.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynamodb_repository.py b/src/dynamodb_repository.py index 180245c..6a58306 100644 --- a/src/dynamodb_repository.py +++ b/src/dynamodb_repository.py @@ -71,7 +71,7 @@ async def save_message( # Utils.insert_data(item) # Exécute l'opération put_item (synchrone) dans un thread séparé - await asyncio.to_thread(self.table.put_item, Item=item) + # await asyncio.to_thread(self.table.put_item, Item=item) return True except Exception as e: Utils.log_error(f"Erreur lors de l'enregistrement dans DynamoDB: {e}")