diff --git a/Backend/token_service/Dockerfile b/Backend/token_service/Dockerfile index 2850af7..bfb85f0 100644 --- a/Backend/token_service/Dockerfile +++ b/Backend/token_service/Dockerfile @@ -6,7 +6,7 @@ ENV LANG=C.UTF-8 # Update and install dependencies # trunk-ignore(hadolint/DL3018) RUN apk add --no-cache postgresql16-client \ - bash supervisor curl openssl \ + bash curl openssl \ build-base libffi-dev python3-dev # Set work directory RUN mkdir /app && chown -R postgres:postgres /app @@ -15,28 +15,21 @@ WORKDIR /app/ # Install Python virtual environment RUN python3 -m venv venv && chown -R postgres:postgres venv - +RUN touch /var/log/django_debug.log && chown -R postgres:postgres /var/log/django_debug.log +RUN touch /var/log/django_error.log && chown -R postgres:postgres /var/log/django_error.log # Copy application code and adjust permissions -COPY --chown=postgres:postgres ./Backend/token_service/supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY --chown=postgres:postgres ./Backend/token_service/requirements.txt /app/ COPY --chown=postgres:postgres ./Backend/token_service/token_service /app/token_service COPY --chown=postgres:postgres ./Backend/token_service/tools.sh /app -COPY --chown=postgres:postgres ./Backend/token_service/run_consumer.sh /app -COPY --chown=postgres:postgres ./Backend/token_service/run_consumer2.sh /app -COPY --chown=postgres:postgres ./Backend/token_service/run_consumer3.sh /app # Ensure supervisord and scripts are executable and owned by postgres -RUN chown -R postgres:postgres /etc/supervisor && \ - chown -R postgres:postgres /usr/bin/supervisord && \ - chown -R postgres:postgres /etc/supervisor/conf.d/supervisord.conf && \ - chown -R postgres:postgres /app && \ +RUN chown -R postgres:postgres /app && \ chown -R postgres:postgres /var/log && \ chown -R postgres:postgres /app/venv && \ - chown -R postgres:postgres /app/token_service && \ - chmod +x /usr/bin/supervisord + chown -R postgres:postgres /app/token_service USER postgres HEALTHCHECK --interval=30s --timeout=2s --start-period=5s --retries=3 CMD curl --fail --silent --write-out http://127.0.0.1:8000/ > /dev/null && echo "success" || echo "failure" -ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] +ENTRYPOINT ["sh", "/app/tools.sh"] diff --git a/Backend/token_service/requirements.txt b/Backend/token_service/requirements.txt index edebbbb..6c7adfc 100644 --- a/Backend/token_service/requirements.txt +++ b/Backend/token_service/requirements.txt @@ -1,5 +1,3 @@ -aio-pika==9.4.2 -aiormq==6.8.0 anyio==4.4.0 asgiref==3.8.1 attrs==24.2.0 @@ -65,4 +63,4 @@ uvloop==0.19.0 watchfiles==0.23.0 websockets==12.0 yarl==1.9.4 -zope.interface==7.0.1 \ No newline at end of file +zope.interface==7.0.1 diff --git a/Backend/token_service/run_consumer.sh b/Backend/token_service/run_consumer.sh deleted file mode 100644 index 4fa4ccf..0000000 --- a/Backend/token_service/run_consumer.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# URL of the Django application -DJANGO_URL="http://localhost:8000/auth/token/refresh/" - -# Wait until Django server is available -while ! curl -s "${DJANGO_URL}" >/dev/null; do - echo "Waiting for Django server at ${DJANGO_URL}..." - sleep 5 -done - -# trunk-ignore(shellcheck/SC1091) -source venv/bin/activate -python3 token_service/consumer.py -echo "Django server is up at ${DJANGO_URL}" -exec "$@" diff --git a/Backend/token_service/run_consumer2.sh b/Backend/token_service/run_consumer2.sh deleted file mode 100644 index 5f16fdd..0000000 --- a/Backend/token_service/run_consumer2.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# URL of the Django application -DJANGO_URL="http://localhost:8000/auth/token/refresh/" - -# Wait until Django server is available -while ! curl -s "${DJANGO_URL}" >/dev/null; do - echo "Waiting for Django server at ${DJANGO_URL}..." - sleep 5 -done - -# trunk-ignore(shellcheck/SC1091) -source venv/bin/activate -python3 token_service/consumer2.py -echo "Django server is up at ${DJANGO_URL}" -exec "$@" diff --git a/Backend/token_service/run_consumer3.sh b/Backend/token_service/run_consumer3.sh deleted file mode 100644 index 284bfc1..0000000 --- a/Backend/token_service/run_consumer3.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# URL of the Django application -DJANGO_URL="http://localhost:8000/auth/token/refresh/" - -# Wait until Django server is available -while ! curl -s "${DJANGO_URL}" >/dev/null; do - echo "Waiting for Django server at ${DJANGO_URL}..." - sleep 5 -done - -# trunk-ignore(shellcheck/SC1091) -source venv/bin/activate -python3 token_service/consumer3.py -echo "Django server is up at ${DJANGO_URL}" -exec "$@" diff --git a/Backend/token_service/supervisord.conf b/Backend/token_service/supervisord.conf deleted file mode 100644 index b954cb2..0000000 --- a/Backend/token_service/supervisord.conf +++ /dev/null @@ -1,30 +0,0 @@ -[supervisord] -nodaemon=true - -[program:django] -command=sh /app/tools.sh -user=postgres -stdout_logfile=/var/log/django.log -stderr_logfile=/var/log/django_err.log -autorestart=true - -[program:consumer] -command= sh /app/run_consumer.sh -user=postgres -stdout_logfile=/var/log/consumer.log -stderr_logfile=/var/log/consumer_err.log -autorestart=true - -# [program:consumer2] -# command= sh /app/run_consumer2.sh -# user=postgres -# stdout_logfile=/var/log/consumer2.log -# stderr_logfile=/var/log/consumer2_err.log -# autorestart=true - -# [program:consumer3] -# command= sh /app/run_consumer3.sh -# user=postgres -# stdout_logfile=/var/log/consumer3.log -# stderr_logfile=/var/log/consumer3_err.log -# autorestart=true \ No newline at end of file diff --git a/Backend/token_service/token_service/consumer.py b/Backend/token_service/token_service/consumer.py deleted file mode 100644 index de2a26d..0000000 --- a/Backend/token_service/token_service/consumer.py +++ /dev/null @@ -1,19 +0,0 @@ -import os -import asyncio -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "token_service.settings") -django.setup() - - -async def start_consumer(): - from token_app.views import CustomTokenObtainPairView, ValidateToken, InvalidateToken - - await asyncio.gather( - CustomTokenObtainPairView().start_consumer(), - ValidateToken().start_consumer(), - InvalidateToken().start_consumer() - ) - -if __name__ == "__main__": - asyncio.run(start_consumer()) diff --git a/Backend/token_service/token_service/consumer2.py b/Backend/token_service/token_service/consumer2.py deleted file mode 100644 index ad6705b..0000000 --- a/Backend/token_service/token_service/consumer2.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "token_service.settings") -django.setup() - - -def start_consumer(): - from token_app.views import InvalidateToken - - InvalidateToken().start_consumer() - - -if __name__ == "__main__": - start_consumer() diff --git a/Backend/token_service/token_service/consumer3.py b/Backend/token_service/token_service/consumer3.py deleted file mode 100644 index dfacffd..0000000 --- a/Backend/token_service/token_service/consumer3.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "token_service.settings") -django.setup() - - -def start_consumer(): - from token_app.views import ValidateToken - - ValidateToken().start_consumer() - - -if __name__ == "__main__": - start_consumer() diff --git a/Backend/token_service/token_service/token_app/rabbitmq_utils.py b/Backend/token_service/token_service/token_app/rabbitmq_utils.py deleted file mode 100644 index b344cea..0000000 --- a/Backend/token_service/token_service/token_app/rabbitmq_utils.py +++ /dev/null @@ -1,74 +0,0 @@ -import json -import aio_pika -import asyncio -from django.conf import settings -import logging - -logger = logging.getLogger(__name__) - -class RabbitMQManager: - _connection = None - - @classmethod - async def get_connection(cls): - if cls._connection is None or cls._connection.is_closed: - cls._connection = await cls._create_connection() - return cls._connection - - @classmethod - async def _create_connection(cls): - try: - connection = await aio_pika.connect_robust( - host=settings.RABBITMQ_HOST, - port=int(settings.RABBITMQ_PORT), - virtualhost="/", - login=settings.RABBITMQ_USER, - password=settings.RABBITMQ_PASS, - heartbeat=600 # 600 seconds or adjust according to your needs - ) - return connection - except Exception as e: - logger.error(f"Error creating RabbitMQ connection: {e}") - raise - -async def publish_message(queue_name, message): - try: - connection = await RabbitMQManager.get_connection() - async with connection.channel() as channel: - queue = await channel.declare_queue(queue_name, durable=True) - # Ensure the message is a JSON string - message = json.dumps(message) if isinstance(message, dict) else message - await channel.default_exchange.publish( - aio_pika.Message( - body=message.encode(), - delivery_mode=aio_pika.DeliveryMode.PERSISTENT, - ), - routing_key=queue_name, - ) - logger.info(f"Message published to queue {queue_name}: {message}") - except aio_pika.exceptions.AMQPConnectionError as e: - logger.error(f"RabbitMQ connection error: {e}") - # Try to reconnect - RabbitMQManager._connection = None - await publish_message(queue_name, message) - except Exception as e: - logger.error(f"Error publishing message: {e}") - -async def consume_message(queue_name, callback): - try: - connection = await RabbitMQManager.get_connection() - async with connection.channel() as channel: - queue = await channel.declare_queue(queue_name, durable=True) - async for message in queue: - async with message.process(): - await callback(message) - logger.info(f"Message consumed from queue {queue_name}: {message.body.decode()}") - # break - except aio_pika.exceptions.AMQPConnectionError as e: - logger.error(f"RabbitMQ connection error: {e}") - # Attempt to reconnect and restart consuming - RabbitMQManager._connection = None - await consume_message(queue_name, callback) - except Exception as e: - logger.error(f"Error consuming message: {e}") - diff --git a/Backend/token_service/token_service/token_app/urls.py b/Backend/token_service/token_service/token_app/urls.py index 3c409f9..7163c55 100644 --- a/Backend/token_service/token_service/token_app/urls.py +++ b/Backend/token_service/token_service/token_app/urls.py @@ -1,7 +1,10 @@ # from django.contrib import admin from django.urls import path -from token_app.views import CustomTokenRefreshView +from token_app.views import CustomTokenRefreshView, CustomTokenObtainPairView, InvalidateToken, ValidateToken urlpatterns = [ - path("token/refresh/", CustomTokenRefreshView.as_view(), name="token_refresh"), + path("token/refresh/", CustomTokenRefreshView.as_view(), name="token_refresh",), + path("token/gen-tokens/", CustomTokenObtainPairView.as_view({"post": "create_token_for_user",}), name="generate_tokens",), + path("token/invalidate-tokens/", InvalidateToken.as_view({"post": "invalidate_token_for_user",}), name="invalidate_tokens",), + path("token/validate-token/", ValidateToken.as_view({"post": "validate_token_for_user",}), name="validate_token",), ] diff --git a/Backend/token_service/token_service/token_app/views.py b/Backend/token_service/token_service/token_app/views.py index 87c3c0b..8012741 100644 --- a/Backend/token_service/token_service/token_app/views.py +++ b/Backend/token_service/token_service/token_app/views.py @@ -3,23 +3,23 @@ from django.http import Http404 from rest_framework import status from token_service import settings -from aio_pika import IncomingMessage from rest_framework.response import Response from django.shortcuts import get_object_or_404 from rest_framework_simplejwt.tokens import RefreshToken from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt -from .rabbitmq_utils import consume_message, publish_message +from rest_framework import viewsets from .serializers import CustomTokenObtainPairSerializer from .models import UserTokens import jwt from channels.db import database_sync_to_async +from rest_framework.permissions import AllowAny logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) -class CustomTokenObtainPairView(TokenObtainPairView): +class CustomTokenObtainPairView(viewsets.ViewSet): """ CustomTokenObtainPairView class to handle token request. @@ -29,71 +29,33 @@ class CustomTokenObtainPairView(TokenObtainPairView): handle_token_request: Method to handle the token request. start_consumer: Method to start the RabbitMQ consumer. """ + permission_classes = [AllowAny] serializer_class = CustomTokenObtainPairSerializer - @staticmethod - async def handle_token_request(message: IncomingMessage): + def create_token_for_user(self, request, *args, **kwargs) -> Response: """ - Method to handle the token request. + Create a new token for the user. - This method processes the token request message received from the user service. - It publishes the token data to the user token request queue. + This method creates a new token for the user and returns the token details. Args: - ch: The channel object. - method: The method object. - properties: The properties object. - body: The body of the message. - """ + request: The request object. - logger.info("Received a message for token request processing.") - try: - data = json.loads(message.body.decode()) - logger.info(f"Decoded message: {data}") - username = data.get("username") - id = data.get("id") - response_message = await CustomTokenObtainPairView.create_token_for_user(username, id) - logger.info(f"Generated response message: {response_message}") - await publish_message("user_token_response_queue", response_message) - logger.info(f"Published response to user_token_response_queue") - except Exception as e: - logger.error(f"Error processing token request: {e}") - - async def start_consumer(self) -> None: - await consume_message("user_token_request_queue", self.handle_token_request) - - @database_sync_to_async - def create_token_for_user(self, username, id): + Returns: + Response: The response object containing the token details. + """ response_message = {} - try: - user, create = UserTokens.objects.get_or_create(id=id, username=username) - logger.info('user= %s', user.username) - logger.info('create= %s', create) - if create: - refresh = RefreshToken.for_user(user) - access_token = str(refresh.access_token) - user.token_data = { - "refresh": str(refresh), - "access": access_token - } - user.save() - response_message = { - "id": id, - "refresh": str(refresh), - "access": access_token - } - else: - token_data = user.token_data - try: - refresh = RefreshToken(token_data["refresh"]) - token_data["access"] = str(refresh.access_token) - user.token_data = token_data - user.save() - response_message = { - "id": id, - "refresh": str(refresh), - "access": token_data["access"] - } - except jwt.ExpiredSignatureError: + status_code = status.HTTP_201_CREATED + id = request.data.get("id") + username = request.data.get("username") + if not username or not id: + response_message = {"error": "Username and id are required"} + status_code = status.HTTP_400_BAD_REQUEST + else: + try: + user, create = UserTokens.objects.get_or_create(id=id, username=username) + logger.info('user= %s', user.username) + logger.info('create= %s', create) + if create: refresh = RefreshToken.for_user(user) access_token = str(refresh.access_token) user.token_data = { @@ -106,11 +68,28 @@ def create_token_for_user(self, username, id): "refresh": str(refresh), "access": access_token } - except Exception as err: - response_message = {"error": str(err)} - except Exception as err: - response_message = {"error": str(err)} - return response_message + else: + token_data = user.token_data + try: + refresh = RefreshToken(token_data["refresh"]) + token_data["access"] = str(refresh.access_token) + user.token_data = token_data + user.save() + response_message = { + "id": id, + "refresh": str(refresh), + "access": token_data["access"] + } + except jwt.ExpiredSignatureError: + response_message = {"erro": "User session has expired, You must login again"} + status_code = status.HTTP_401_UNAUTHORIZED + except Exception as err: + response_message = {"error": str(err)} + status_code = status.HTTP_400_BAD_REQUEST + except Exception as err: + response_message = {"error": str(err)} + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + return Response(response_message, status=status_code) class CustomTokenRefreshView(TokenRefreshView): def post(self, request, *args, **kwargs) -> Response: @@ -140,7 +119,7 @@ def post(self, request, *args, **kwargs) -> Response: return Response({"error": "Could not generate access token", "details": str(err)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) -class ValidateToken(): +class ValidateToken(viewsets.ViewSet): def validate_token(self, access_token) -> bool: """ @@ -163,82 +142,69 @@ def validate_token(self, access_token) -> bool: except jwt.InvalidTokenError: return False - @database_sync_to_async - def validate_token_for_user(self, access_token, id): + def validate_token_for_user(self, request, *args, **kwargs): + try: - result = self.validate_token(access_token) + status_code = status.HTTP_200_OK + response_message = {} + access = request.data.get("access") + id = request.data.get("id") + if not access or not id: + response_message = {"error": "Access token and id are required"} + status_code = status.HTTP_400_BAD_REQUEST + result = self.validate_token(access) if result: logger.info("result= %s", result) - user = UserTokens.objects.filter(id = id, token_data__access = access_token).first() + user = UserTokens.objects.filter(id = id, token_data__access = access).first() logger.info("user.username= %s", user.username) logger.info("user.token_data['access']= %s", user.token_data["access"]) if result: - response = {"access_token": "Valid token"} + response_message = {"access_token": "Valid token"} else: - response = {"error": "token mismatch"} + response_message = {"error": "token mismatch"} + status_code = status.HTTP_401_UNAUTHORIZED except jwt.ExpiredSignatureError: - response = {"error": "token is expired"} + response_message = {"error": "token is expired"} + status_code = status.HTTP_401_UNAUTHORIZED except jwt.InvalidTokenError: - response = {"error": "Invalid token"} + response_message = {"error": "Invalid token"} + status_code = status.HTTP_401_UNAUTHORIZED except Http404: - response = {"error": "User has not logged in yet!!"} + response_message = {"error": "User has not logged in yet!!"} + status_code = status.HTTP_401_UNAUTHORIZED except Exception as err: - response = {"error": str(err)} - logger.info("response= %s", response) - return response - - # @staticmethod - async def validate_token_request_queue(self, message: IncomingMessage): - """ - Method to handle the token validation request. - - This method processes the token validation request message received from the user service. - It validates the access token and sends the response back to the user service. + response_message = {"error": str(err)} + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + logger.info("response_message= %s", response_message) + return Response(response_message, status=status_code) - Args: - ch: The channel object. - method: The method object. - properties: The properties object. - body: The body of the message. - """ - data = json.loads(message.body.decode()) - access_token = data.get("access") - id = data.get("id") - response = await self.validate_token_for_user(access_token, id) - logger.info("response = %s", response) - await publish_message("validate_token_response_queue", json.dumps(response)) - - async def start_consumer(self) -> None: - await consume_message("validate_token_request_queue", self.validate_token_request_queue) - -class InvalidateToken(): - @database_sync_to_async - def invalidate_token_for_user(self, access, id): +class InvalidateToken(viewsets.ViewSet): + def invalidate_token_for_user(self, request, *args, **kwargs) -> Response: try: - check_token = ValidateToken() - if check_token.validate_token(access): - user = get_object_or_404(UserTokens, id=id) - if user is not None: - user.delete() - response_message = {"detail":"User logged out"} + status_code = status.HTTP_200_OK + access = request.data.get("access") + id = request.data.get("id") + if not access or not id: + response_message = {"error": "Access token and id are required"} + status_code = status.HTTP_400_BAD_REQUEST + else: + check_token = ValidateToken() + if check_token.validate_token(access): + user = get_object_or_404(UserTokens, id=id) + if user is not None: + user.delete() + response_message = {"detail":"User logged out"} except jwt.ExpiredSignatureError: response_message = {"error": "Access token is expired"} + status_code = status.HTTP_401_UNAUTHORIZED except jwt.InvalidTokenError: response_message = {"error": "Invalid access token"} + status_code = status.HTTP_401_UNAUTHORIZED except Http404: response_message = {"error": "User has not logged in yet"} + status_code = status.HTTP_401_UNAUTHORIZED except Exception as err: response_message = {"error": str(err)} - return response_message - - @staticmethod - async def handle_logout_request_queue(message: IncomingMessage): - data = json.loads(message.body.decode()) - access = data.get("access") - id = data.get("id") - response_message = await InvalidateToken.invalidate_token_for_user(access, id) - await publish_message("logout_response_queue", json.dumps(response_message)) - - async def start_consumer(self): - await consume_message("logout_request_queue", self.handle_logout_request_queue) + status_code = status.HTTP_500_INTERNAL_SERVER_ERROR + return Response(response_message, status=status_code) diff --git a/Backend/token_service/token_service/token_service/settings.py b/Backend/token_service/token_service/token_service/settings.py index e4c7ae9..8617c4f 100644 --- a/Backend/token_service/token_service/token_service/settings.py +++ b/Backend/token_service/token_service/token_service/settings.py @@ -14,10 +14,53 @@ from datetime import timedelta from pathlib import Path -RABBITMQ_HOST = os.getenv("RABBITMQ_HOST") -RABBITMQ_USER = os.getenv("RABBITMQ_USER") -RABBITMQ_PASS = os.getenv("RABBITMQ_PASS") -RABBITMQ_PORT = os.getenv("RABBITMQ_PORT") +LOG_DIR = Path('/var/log/') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {message}', + 'style': '{', + }, + 'simple': { + 'format': '{levelname} {message}', + 'style': '{', + }, + }, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': os.path.join(LOG_DIR, 'django_debug.log'), + 'formatter': 'verbose', + }, + 'error_file': { + 'level': 'ERROR', + 'class': 'logging.FileHandler', + 'filename': os.path.join(LOG_DIR, 'django_error.log'), + 'formatter': 'verbose', + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file', 'console'], + 'level': 'DEBUG', + 'propagate': True, + }, + 'django.request': { + 'handlers': ['error_file'], + 'level': 'ERROR', + 'propagate': False, + }, + }, +} # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent diff --git a/Backend/user_service/Dockerfile b/Backend/user_service/Dockerfile index 0d2cd47..362148e 100644 --- a/Backend/user_service/Dockerfile +++ b/Backend/user_service/Dockerfile @@ -7,37 +7,36 @@ ENV LANG=C.UTF-8 # trunk-ignore(hadolint/DL3018) RUN addgroup -g 1000 postgres && adduser -D -u 1000 -G postgres postgres -RUN apk add --no-cache supervisor \ - curl openssl bash postgresql16-client \ +RUN apk add --no-cache curl openssl bash \ + postgresql16-client \ build-base libffi-dev python3-dev # Set work directory -RUN mkdir /app && chown -R postgres:postgres /app && mkdir -p /app/www/avatars && chown -R postgres:postgres /app/www/avatars +RUN mkdir /app && chown -R postgres:postgres /app && \ +mkdir -p /app/www/avatars && chown -R postgres:postgres /app/www/avatars WORKDIR /app/ # Install Python virtual environment RUN python3 -m venv venv && chown -R postgres:postgres venv +RUN touch /var/log/django_debug.log && chown -R postgres:postgres /var/log/django_debug.log +RUN touch /var/log/django_error.log && chown -R postgres:postgres /var/log/django_error.log # Copy application code and adjust permissions -COPY --chown=postgres:postgres ./Backend/user_service/supervisord.conf /etc/supervisor/conf.d/supervisord.conf + COPY --chown=postgres:postgres ./Backend/user_service/requirements.txt /app/ COPY --chown=postgres:postgres ./Backend/user_service/user_service /app/user_service COPY --chown=postgres:postgres ./Backend/user_service/tools.sh /app -COPY --chown=postgres:postgres ./Backend/user_service/run_consumer.sh /app + # Ensure supervisord and scripts are executable and owned by postgres -RUN chown -R postgres:postgres /etc/supervisor && \ - chown -R postgres:postgres /usr/bin/supervisord && \ - chown -R postgres:postgres /etc/supervisor/conf.d/supervisord.conf && \ - chown -R postgres:postgres /app && \ +RUN chown -R postgres:postgres /app && \ chown -R postgres:postgres /var/log && \ chown -R postgres:postgres /app/venv && \ - chown -R postgres:postgres /app/user_service && \ - chmod +x /usr/bin/supervisord + chown -R postgres:postgres /app/user_service USER postgres HEALTHCHECK --interval=30s --timeout=2s --start-period=5s --retries=3 CMD curl -sSf http://localhost:8001/user/register/ > /dev/null && echo "success" || echo "failure" -ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"] +ENTRYPOINT ["sh", "/app/tools.sh"] diff --git a/Backend/user_service/requirements.txt b/Backend/user_service/requirements.txt index edebbbb..6c7adfc 100644 --- a/Backend/user_service/requirements.txt +++ b/Backend/user_service/requirements.txt @@ -1,5 +1,3 @@ -aio-pika==9.4.2 -aiormq==6.8.0 anyio==4.4.0 asgiref==3.8.1 attrs==24.2.0 @@ -65,4 +63,4 @@ uvloop==0.19.0 watchfiles==0.23.0 websockets==12.0 yarl==1.9.4 -zope.interface==7.0.1 \ No newline at end of file +zope.interface==7.0.1 diff --git a/Backend/user_service/run_consumer.sh b/Backend/user_service/run_consumer.sh deleted file mode 100644 index ba126a7..0000000 --- a/Backend/user_service/run_consumer.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -# URL of the Django application -DJANGO_URL="http://localhost:8001/user/register/" - -# Wait until Django server is available -while ! curl -s "${DJANGO_URL}" >/dev/null; do - echo "Waiting for Django server at ${DJANGO_URL}..." - sleep 5 -done - -# trunk-ignore(shellcheck/SC1091) -source venv/bin/activate -python3 /app/user_service/consumer.py -echo "Django server is up at ${DJANGO_URL}" -exec "$@" diff --git a/Backend/user_service/supervisord.conf b/Backend/user_service/supervisord.conf deleted file mode 100644 index 4001c24..0000000 --- a/Backend/user_service/supervisord.conf +++ /dev/null @@ -1,16 +0,0 @@ -[supervisord] -nodaemon=true - -[program:django] -command=sh /app/tools.sh -user=postgres -stdout_logfile=/var/log/django.log -stderr_logfile=/var/log/django_err.log -autorestart=true - -# [program:consumer] -# command= sh /app/run_consumer.sh -# user=postgres -# stdout_logfile=/var/log/consumer.log -# stderr_logfile=/var/log/consumer_err.log -# autorestart=true \ No newline at end of file diff --git a/Backend/user_service/tools.sh b/Backend/user_service/tools.sh index 01f5a4a..3223a31 100644 --- a/Backend/user_service/tools.sh +++ b/Backend/user_service/tools.sh @@ -14,8 +14,6 @@ done export DJANGO_SETTINGS_MODULE=user_service.settings python3 /app/user_service/manage.py makemigrations user_app python3 /app/user_service/manage.py migrate -# echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('${DB_USER}', 'admin@example.com', '${DB_USER}')" | python3 /app/user_service/manage.py shell && echo "Superuser created successfully." -# python3 /app/user_service/manage.py runserver 0.0.0.0:8001 cd /app/user_service # exec uvicorn user_service.asgi:application --host 0.0.0.0 --port 8001 -daphne -b 0.0.0.0 -p 8001 user_service.asgi:application \ No newline at end of file +daphne -b 0.0.0.0 -p 8001 user_service.asgi:application diff --git a/Backend/user_service/user_service/consumer.py b/Backend/user_service/user_service/consumer.py deleted file mode 100644 index 8efea9b..0000000 --- a/Backend/user_service/user_service/consumer.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -import django - -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "user_service.settings") -django.setup() - - -def start_consumer(): - from user_app.views import UserViewSet - - UserViewSet().start_consumer() - - -if __name__ == "__main__": - start_consumer() diff --git a/Backend/user_service/user_service/user_app/rabbitmq_utils.py b/Backend/user_service/user_service/user_app/rabbitmq_utils.py deleted file mode 100644 index d4fcf82..0000000 --- a/Backend/user_service/user_service/user_app/rabbitmq_utils.py +++ /dev/null @@ -1,73 +0,0 @@ -import json -import aio_pika -import asyncio -from django.conf import settings -import logging - -logger = logging.getLogger(__name__) - -class RabbitMQManager: - _connection = None - - @classmethod - async def get_connection(cls): - if cls._connection is None or cls._connection.is_closed: - cls._connection = await cls._create_connection() - return cls._connection - - @classmethod - async def _create_connection(cls): - try: - connection = await aio_pika.connect_robust( - host=settings.RABBITMQ_HOST, - port=int(settings.RABBITMQ_PORT), - virtualhost="/", - login=settings.RABBITMQ_USER, - password=settings.RABBITMQ_PASS, - heartbeat=600 # 600 seconds or adjust according to your needs - ) - return connection - except Exception as e: - logger.error(f"Error creating RabbitMQ connection: {e}") - raise - -async def publish_message(queue_name, message): - try: - connection = await RabbitMQManager.get_connection() - async with connection.channel() as channel: - queue = await channel.declare_queue(queue_name, durable=True) - # Ensure the message is a JSON string - message = json.dumps(message) if isinstance(message, dict) else message - await channel.default_exchange.publish( - aio_pika.Message( - body=message.encode(), - delivery_mode=aio_pika.DeliveryMode.PERSISTENT, - ), - routing_key=queue_name, - ) - logger.info(f"Message published to queue {queue_name}: {message}") - except aio_pika.exceptions.AMQPConnectionError as e: - logger.error(f"RabbitMQ connection error: {e}") - # Try to reconnect - RabbitMQManager._connection = None - await publish_message(queue_name, message) - except Exception as e: - logger.error(f"Error publishing message: {e}") - -async def consume_message(queue_name, callback): - try: - connection = await RabbitMQManager.get_connection() - async with connection.channel() as channel: - queue = await channel.declare_queue(queue_name, durable=True) - async for message in queue: - async with message.process(): - await callback(message) - logger.info(f"Message consumed from queue {queue_name}: {message.body.decode()}") - break - except aio_pika.exceptions.AMQPConnectionError as e: - logger.error(f"RabbitMQ connection error: {e}") - # Attempt to reconnect and restart consuming - RabbitMQManager._connection = None - await consume_message(queue_name, callback) - except Exception as e: - logger.error(f"Error consuming message: {e}") diff --git a/Backend/user_service/user_service/user_app/urls.py b/Backend/user_service/user_service/user_app/urls.py index 62aa48c..84425fc 100644 --- a/Backend/user_service/user_service/user_app/urls.py +++ b/Backend/user_service/user_service/user_app/urls.py @@ -7,7 +7,7 @@ path("user/register/",RegisterViewSet.as_view({"post": "create_user",}),name="user-register",), path("user/",UserViewSet.as_view({"get": "users_list",}),name="users-list",), path("user/login/",UserLoginView.as_view({"post": "login",}),name="user-login",), - path("user//",UserViewSet.as_view({"get": "retrieve_user","put": "update_user","delete": "destroy_user",}),name="user-detail",), + path("user//",UserViewSet.as_view({"get": "retrieve_user","patch": "update_user","delete": "destroy_user",}),name="user-detail",), path("user//logout/", UserLogoutView.as_view({"post": "logout",}),name="user-logout",), path("user//friends/", FriendsViewSet.as_view({"get": "friends_list"}), name="friends-list"), path("user//request/", FriendsViewSet.as_view({"post": "send_friend_request"}), name="send-request"), @@ -15,5 +15,4 @@ path("user//pending/", FriendsViewSet.as_view({"get": "friend_requests"}), name="friend-request-list"), path("user//reject//", FriendsViewSet.as_view({"put": "reject_friend_request"}), name="reject-request"), path("user//friends//remove/", FriendsViewSet.as_view({"delete": "remove_friend"}), name="remove-friend"), - # path('chat//', chat_Page, name='chat'), ] diff --git a/Backend/user_service/user_service/user_app/user_session_views.py b/Backend/user_service/user_service/user_app/user_session_views.py index 7937f68..af33de5 100644 --- a/Backend/user_service/user_service/user_app/user_session_views.py +++ b/Backend/user_service/user_service/user_app/user_session_views.py @@ -62,7 +62,8 @@ def logout(self, request, pk=None): else: bearer = request.headers.get("Authorization") if not bearer or not bearer.startswith('Bearer '): - return Response({"detail": "Access token is required"}, code=status.HTTP_400_BAD_REQUEST) + response_message = {"detail": "Access token is required"} + status_code =status.HTTP_400_BAD_REQUEST access_token = bearer.split(' ')[1] data = {"id":pk, "access": access_token} response_data = requests.post("http://token-service:8000/auth/token/invalidate-tokens/", data=data) diff --git a/Backend/user_service/user_service/user_app/views.py b/Backend/user_service/user_service/user_app/views.py index 962c07c..b655216 100644 --- a/Backend/user_service/user_service/user_app/views.py +++ b/Backend/user_service/user_service/user_app/views.py @@ -11,12 +11,10 @@ from .models import UserProfileModel, FriendRequest from rest_framework.parsers import MultiPartParser, FormParser from rest_framework.decorators import parser_classes -from .rabbitmq_utils import publish_message, consume_message from .serializers import UserSerializer, FriendSerializer -from aio_pika.message import IncomingMessage -from asgiref.sync import async_to_sync +import requests -async def validate_token(request) -> None: +def validate_token(request) -> None: bearer = request.headers.get("Authorization") if not bearer or not bearer.startswith('Bearer '): raise ValidationError( @@ -24,31 +22,10 @@ async def validate_token(request) -> None: code=status.HTTP_400_BAD_REQUEST) access_token = bearer.split(' ')[1] data = {"id": request.user.id, "access": access_token} - await publish_message("validate_token_request_queue", json.dumps(data)) - - response_data = {} - - async def handle_response(message: IncomingMessage): - nonlocal response_data - response_data.update(json.loads(message.body.decode())) - - await consume_message("validate_token_response_queue", handle_response) - + response = requests.post("http://token-service:8000/auth/token/validate-token/", data=data) + response_data = response.json() if "error" in response_data: - raise ValidationError(detail={"error": "Invalid access token"}, - code=status.HTTP_401_UNAUTHORIZED - ) - -# def chat_Page(request, username): -# user_obj = User.objects.get(username=username) -# users = User.objects.exclude(username=request.user.username) - -# if request.user.pk > user_obj.pk: # to make sure the thread name is always the same for both users -# thread_name = f'chat_{request.user.pk}-{user_obj.pk}' # thread name should be unique for each chat room to avoid mixing messages -# else: -# thread_name = f'chat_{user_obj.pk}-{request.user.pk}' -# message_objs = ChatModel.objects.filter(thread_name=thread_name) # get all messages in the chat room with the same thread name -# return render(request, 'chat.html', {'users': users, 'user_obj': user_obj, 'messages': message_objs, 'thread_name': thread_name}) + raise ValidationError(detail=response_data, code=response_data.get("status_code")) class UserViewSet(viewsets.ViewSet): """ @@ -86,7 +63,7 @@ def users_list(self, request) -> Response: Response: The response object containing the list of users. """ try: - async_to_sync(validate_token)(request) + validate_token(request) users = UserProfileModel.objects.all() serializer = UserSerializer(users, many=True) return Response(serializer.data, status=status.HTTP_200_OK) @@ -108,10 +85,10 @@ def retrieve_user(self, request, pk=None) -> Response: Response: The response object containing the user data. """ try: - async_to_sync(validate_token)(request) + validate_token(request) data = get_object_or_404(UserProfileModel, id=pk) if request.user != data: - return Response({"detail": "You're not authorized"}, status=status.HTTP) + return Response({"detail": "You're not authorized"}, status=status.HTTP_401_UNAUTHORIZED) serializer = UserSerializer(data) return Response(serializer.data, status=status.HTTP_200_OK) except Exception as err: @@ -132,7 +109,7 @@ def update_user(self, request, pk=None) -> Response: Response: The response object containing the updated user data. """ try: - async_to_sync(validate_token)(request) + validate_token(request) data = get_object_or_404(UserProfileModel, id=pk) if data != request.user and not request.user.is_superuser: return Response(status=status.HTTP_401_UNAUTHORIZED) @@ -162,7 +139,7 @@ def destroy_user(self, request, pk=None) -> Response: Response: The response object containing the status of the deletion. """ try: - async_to_sync(validate_token)(request) + validate_token(request) data = get_object_or_404(UserProfileModel, id=pk) if data != request.user and not request.user.is_superuser: return Response(status=status.HTTP_401_UNAUTHORIZED) @@ -220,7 +197,7 @@ class FriendsViewSet(viewsets.ViewSet): def friends_list(self, request, user_pk=None): try: - async_to_sync(validate_token)(request) + validate_token(request) user = get_object_or_404(UserProfileModel, id=user_pk) serializer = UserSerializer(user.friends.all(), many=True) data = [{"id": item["id"], "username": item["username"], "status": item["online_status"]} for item in serializer.data] @@ -233,7 +210,7 @@ def friends_list(self, request, user_pk=None): def remove_friend(self, request, user_pk=None, pk=None): try: - async_to_sync(validate_token)(request) + validate_token(request) user = get_object_or_404(UserProfileModel, id=user_pk) friend = get_object_or_404(UserProfileModel, id=pk) if friend in user.friends.all(): @@ -251,7 +228,7 @@ def send_friend_request(self, request, user_pk=None): response_message = {} status_code = 0 try: - async_to_sync(validate_token)(request) + validate_token(request) current_user = get_object_or_404(UserProfileModel, id=user_pk) current_user_friends = current_user.friends.all() friend_username = request.data.get("username") @@ -292,7 +269,7 @@ def send_friend_request(self, request, user_pk=None): def accept_friend_request(self, request, user_pk=None, pk=None): try: - async_to_sync(validate_token)(request) + validate_token(request) current_user = get_object_or_404(UserProfileModel, id=user_pk) sender_user = get_object_or_404(UserProfileModel, id=pk) pending_requests = FriendRequest.objects.filter(receiver_user=current_user, sender_user=sender_user, status='pending') @@ -309,7 +286,7 @@ def accept_friend_request(self, request, user_pk=None, pk=None): def reject_friend_request(self, request, user_pk=None, pk=None): try: - async_to_sync(validate_token)(request) + validate_token(request) current_user = get_object_or_404(UserProfileModel, id=user_pk) sender_user = get_object_or_404(UserProfileModel, id=pk) pending_request = FriendRequest.objects.filter(receiver_user=current_user, sender_user=sender_user, status='pending').first() @@ -325,7 +302,7 @@ def reject_friend_request(self, request, user_pk=None, pk=None): def friend_requests(self, request, user_pk=None): # get user pending list try: - async_to_sync(validate_token)(request) + validate_token(request) user = get_object_or_404(UserProfileModel, id = user_pk) pending_requests = FriendRequest.objects.filter(receiver_user=user, status='pending') # filter returns a list data = FriendSerializer(pending_requests, many=True) diff --git a/Backend/user_service/user_service/user_service/settings.py b/Backend/user_service/user_service/user_service/settings.py index 7eaab3d..8c31b52 100644 --- a/Backend/user_service/user_service/user_service/settings.py +++ b/Backend/user_service/user_service/user_service/settings.py @@ -15,10 +15,53 @@ from pathlib import Path import re -RABBITMQ_HOST = os.getenv("RABBITMQ_HOST") -RABBITMQ_USER = os.getenv("RABBITMQ_USER") -RABBITMQ_PASS = os.getenv("RABBITMQ_PASS") -RABBITMQ_PORT = os.getenv("RABBITMQ_PORT") +LOG_DIR = Path('/var/log/') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'verbose': { + 'format': '{levelname} {asctime} {module} {message}', + 'style': '{', + }, + 'simple': { + 'format': '{levelname} {message}', + 'style': '{', + }, + }, + 'handlers': { + 'file': { + 'level': 'DEBUG', + 'class': 'logging.FileHandler', + 'filename': os.path.join(LOG_DIR, 'django_debug.log'), + 'formatter': 'verbose', + }, + 'error_file': { + 'level': 'ERROR', + 'class': 'logging.FileHandler', + 'filename': os.path.join(LOG_DIR, 'django_error.log'), + 'formatter': 'verbose', + }, + 'console': { + 'level': 'DEBUG', + 'class': 'logging.StreamHandler', + 'formatter': 'simple', + }, + }, + 'loggers': { + 'django': { + 'handlers': ['file', 'console'], + 'level': 'DEBUG', + 'propagate': True, + }, + 'django.request': { + 'handlers': ['error_file'], + 'level': 'ERROR', + 'propagate': False, + }, + }, +} # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent @@ -122,27 +165,6 @@ ASGI_APPLICATION = "user_service.asgi.application" - -# CHANNEL_LAYERS = { -# "default": { -# "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", -# "CONFIG": { -# "hosts": [("localhost", 6379)], -# }, -# }, -# } - -# CHANNEL_LAYERS = { -# "default": { -# "BACKEND": "channels_redis.pubsub.RedisPubSubChannelLayer", -# "CONFIG": { -# "hosts":[{ -# "address": "redis://redis:6379", -# "ssl_cert_reqs": None,}], -# }, -# }, -# } - # Database # https://docs.djangoproject.com/en/5.0/ref/settings/#databases @@ -162,26 +184,6 @@ }, } -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'handlers': { - 'console': { - 'level': 'DEBUG', - 'class': 'logging.StreamHandler', - }, - }, - 'loggers': { - 'django': { - 'handlers': ['console'], - 'level': 'DEBUG', - 'propagate': True, - }, - }, -} - - - # Password validation # https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators @@ -231,6 +233,7 @@ # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field + CHANNEL_LAYERS = { 'default': { 'BACKEND': 'channels_redis.core.RedisChannelLayer', @@ -240,14 +243,9 @@ }, } -# CHANNEL_LAYERS = { -# 'default':{ -# 'BACKEND':'channels.layers.InMemoryChannelLayer' -# } -# } - CORS_ORIGIN_ALLOW_ALL = True AUTH_USER_MODEL = "user_app.UserProfileModel" + AUTHENTICATION_BACKENDS = ( 'django.contrib.auth.backends.ModelBackend', ) diff --git a/Makefile b/Makefile index c8aeccd..f296478 100644 --- a/Makefile +++ b/Makefile @@ -106,7 +106,7 @@ dlog: echo "Error: No container name provided."; \ exit 1; \ fi - docker exec -it $(filter-out $@,$(MAKECMDGOALS)) bash -c 'cat /var/log/django.log' + docker exec -it $(filter-out $@,$(MAKECMDGOALS)) bash -c 'cat /var/log/django_debug.log' .PHONY: dlog-err dlog-err: @@ -114,7 +114,7 @@ dlog-err: echo "Error: No container name provided."; \ exit 1; \ fi - docker exec -it $(filter-out $@,$(MAKECMDGOALS)) bash -c 'cat /var/log/django_err.log' + docker exec -it $(filter-out $@,$(MAKECMDGOALS)) bash -c 'cat /var/log/django_error.log' .PHONY: clog clog: diff --git a/sample env b/sample env index 4c07651..a0461b8 100644 --- a/sample env +++ b/sample env @@ -5,12 +5,4 @@ DB_USER = "root" DB_PASS = "root" -RABBITMQ_DEFAULT_USER="user" -RABBITMQ_DEFAULT_PASS="pass" - -RABBITMQ_HOST="rabbitmq" -RABBITMQ_USER="user" -RABBITMQ_PASS="pass" -RABBITMQ_PORT="5672" - -PGPASSWORD='root' \ No newline at end of file +PGPASSWORD='root'