Skip to content

Commit

Permalink
Merge pull request #69 from abbastoof/feature/045-matchmaking
Browse files Browse the repository at this point in the history
Feature/045 matchmaking
  • Loading branch information
abbastoof authored Aug 16, 2024
2 parents 8d20f6f + 291a73d commit 5830e41
Show file tree
Hide file tree
Showing 15 changed files with 297 additions and 118 deletions.
27 changes: 13 additions & 14 deletions Backend/game_history/game_history/game_history/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,13 @@
import os
from datetime import timedelta
from pathlib import Path
from dotenv import load_dotenv

load_dotenv()

PGSQL_HOST = os.environ.get('PGSQL_HOST')
DB_USER = os.environ.get('DB_USER')
DB_PASS = os.environ.get('DB_PASS')
LOG_DIR = Path('/var/log/')

LOGGING = {
Expand Down Expand Up @@ -69,10 +75,10 @@
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-woftd2en2**zr(b%#*2vit2v%s@(k54gb^c(ots0abo7(wsmo%"
SECRET_KEY = os.environ.get('DJANGO_SECRET')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False

ALLOWED_HOSTS = [
'localhost',
Expand All @@ -82,7 +88,6 @@
'game-history:8002',
]


# Application definition

INSTALLED_APPS = [
Expand Down Expand Up @@ -112,12 +117,6 @@
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
),
# 'DEFAULT_RENDERER_CLASSES': [
# 'rest_framework.renderers.JSONRenderer',
# ],
# 'DEFAULT_PARSER_CLASSES': [
# 'rest_framework.parsers.JSONParser',
# ],
}

MIDDLEWARE = [
Expand Down Expand Up @@ -159,15 +158,15 @@
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "game_history",
"HOST": "postgresql",
"USER": "root",
"PASSWORD": "root",
"HOST": PGSQL_HOST,
"USER": DB_USER,
"PASSWORD": DB_PASS,
"PORT": "5432",
"ATOMIC_REQUESTS": True,
"TEST": {
"NAME": "test_game_history",
"USER": "root",
"PASSWORD": "root",
"USER": DB_USER,
"PASSWORD": DB_PASS,
"PORT": "5432",
"ATOMIC_REQUESTS": True,
},
Expand Down
16 changes: 14 additions & 2 deletions Backend/token_service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Also the token service is responsible for refreshing the access token.

## Docker container configuration

Every single REST API endpoint has their own database. The database is a PostgreSQL database. The database name for all the endpoints is `postgres`. The database user and password for all the endpoints is inside the env file. The database port for all the endpoints is `5432`.
Every single REST API endpoint has their own database. The database is a PostgreSQL database. The database name for this endpoints is `token_service`. The database user and password for all the endpoints is inside the env file. The database port for all the endpoints is `5432`.

The requirements package is inside the requirements.txt file.
The tools.sh file is used to run the init_database.sh file and run the API.
Expand All @@ -15,5 +15,17 @@ The API runs on port 8000 and exposed to 8001.

## Tutorial to use the token_service

You can use the token_service by sending a POST request to the https://localhost:3000/auth/token/refresh/ endpoint with the refresh token in the header as a Bearer token and it will return a new access token.
There are three endpoints in the token_service. The endpoints are:
- `auth/token/refresh/` - This endpoint is used to refresh the access token.
- `auth/token/gen-tokens/` - This endpoint is used to generate the refresh and access tokens.
- `auth/token/invalidate-tokens/` - This endpoint is used by user-service logout or delete user to invalidate the refresh and access tokens.
- `auth/token/validate-token/` - This endpoint is used to validate the access token.

## The UserTokens model

The UserTokens model is used to store the refresh and access tokens. The UserTokens model has the following fields:
| Field | Type | Description |
| ---------- | ---------| ----------------------------- |
| id | Integer | The id of the user token |
| username | String | The username of the user |
| token_data | JSON | The refresh and access tokens |
94 changes: 53 additions & 41 deletions Backend/token_service/token_service/token_app/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@
from .models import UserTokens
import jwt
from rest_framework.permissions import AllowAny
from dotenv import load_dotenv
import os

load_dotenv()
SECRET = os.environ.get('DJANGO_SECRET')

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -40,52 +45,59 @@ def post(self, request, *args, **kwargs) -> Response:
Returns:
Response: The response object containing the token details.
"""
response_message = {}
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 = {
"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
secret_key = request.headers.get('X-SERVICE-SECRET')
if secret_key == SECRET:
response_message = {}
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 = {
"refresh": str(refresh),
"access": access_token
}
user.save()
response_message = {
"id": id,
"refresh": str(refresh),
"access": token_data["access"]
"access": access_token
}
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
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
else:
logger.info('Invalid secret keys = %s', response_message)
response_message = {"error": "Unauthorized request"}
status_code = status.HTTP_401_UNAUTHORIZED
logger.info('response_message = %s', response_message)
return Response(response_message, status=status_code)

class CustomTokenRefreshView(TokenRefreshView):
Expand Down
25 changes: 18 additions & 7 deletions Backend/token_service/token_service/token_service/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@
import os
from datetime import timedelta
from pathlib import Path
from dotenv import load_dotenv
load_dotenv()

USER_SERVICE_URL = os.environ.get('USER_SERVICE')
GAME_HISTORY_URL = os.environ.get('GAME_HISTORY')
PGSQL_HOST = os.environ.get('PGSQL_HOST')
DB_USER = os.environ.get('DB_USER')
DB_PASS = os.environ.get('DB_PASS')

LOG_DIR = Path('/var/log/')

Expand Down Expand Up @@ -70,10 +78,10 @@
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = "django-insecure-woftd2en2**zr(b%#*2vit2v%s@(k54gb^c(ots0abo7(wsmo%"
SECRET_KEY = os.environ.get('DJANGO_SECRET')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
DEBUG = False

ALLOWED_HOSTS = [
'localhost',
Expand Down Expand Up @@ -114,7 +122,6 @@
"rest_framework_simplejwt.authentication.JWTAuthentication",
),
}

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
Expand Down Expand Up @@ -153,9 +160,9 @@
"default": {
"ENGINE": "django.db.backends.postgresql",
"NAME": "token_service",
"HOST": "postgresql",
"USER": "root",
"PASSWORD": "root",
"HOST": PGSQL_HOST,
"USER": DB_USER,
"PASSWORD": DB_PASS,
"PORT": "5432",
}
}
Expand Down Expand Up @@ -201,4 +208,8 @@
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
CORS_ORIGIN_ALLOW_ALL = True
CORS_ORIGIN_ALLOW_ALL = False
CORS_ALLOWED_ORIGINS = [
USER_SERVICE_URL,
GAME_HISTORY_URL,
]
53 changes: 41 additions & 12 deletions Backend/user_service/user_service/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@ Upon GET,PUT and DELETE requests for a user record, the user service will retrie

## Docker container configuration

Every single REST API endpoint has their own database. The database is a PostgreSQL database. The database name for all the endpoints is `postgres`. The database user and password for all the endpoints is inside the env file. The database port for all the endpoints is `5432`.
Every single REST API endpoint has their own database. The database is a PostgreSQL database. The database name for this endpoints is `user_service`. The database user and password for all the endpoints is inside the env file. The database port for all the endpoints is `5432`.

The requirements package is inside the requirements.txt file.
The run_consumer.sh file is used to run the consumer.py file inside the user_management/user_management folder. The consumer.py file is used to consume the message from the RabbitMQ message broker and check if the username and password are correct.
The tools.sh and run_consumer.sh files are running inside the Docker container using the supervisord service. The supervisord service is used to run multiple services inside the Docker container.
The tools.sh file is used to run the init_database.sh file and run the API.
The tools.sh file is used to run the API.
The API runs inside a virtual environment. The virtual environment is created inside the Docker container using command python3.12 -m venv venv. The virtual environment is activated using command source venv/bin/activate inside the tools.sh file.

The API runs on port 8000 and exposed to 8000.
The API runs on port 8000.

## Tutorial to use the user_service

Expand All @@ -33,7 +31,22 @@ You should send a JSON object with the following fields:

- `http://localhost:3000/user/` "List users records using GET method"
- `http://localhost:3000/user/<int:pk>/` "without angel brackets" "retrieve, update and delete user record using GET, PUT and DELETE methods respectively"
You can enable otp by sending a JSON object with the following fields:
```JSON
{
"otp_status": "True"
}
```
- `http://localhost:3000/user/login/` "login user using POST method"
- `http://localhost:3000/user/verifyotp/` "send user otp using POST method"
You should send a JSON object with the following fields:
```JSON
{
"username": "username",
"password": "password",
"otp": "otp"
}
```
- `http://localhost:3000/user/logout/` "logout user using POST method"
- `http://localhost:3000/user/<int:user_pk>/friends/` "List friends of a user using GET method"
The endpoint will return value is a JSON object with the following fields:
Expand All @@ -49,7 +62,7 @@ The endpoint will return value is a JSON object with the following fields:
- `http://localhost:3000/user/<int:user_pk>/request/` send friend request to a user in a JSON object using POST method the JSON object should contain the following fields:
```JSON
{
"username": "username",
"username": "username"
}
```
- `http://localhost:3000/user/<int:user_pk>/accept/<int:pk>/` accept friend request PUT method
Expand All @@ -76,12 +89,17 @@ The username and email are unique.
The User table consists of the following fields:
You can find it in user_management/user_management/users/models.py

| Field Name | Data Type | Description |
| ---------- | --------- | ---------------------------------- |
| id | Integer | Primary Key |
| username | String | User Name |
| email | String | User Email |
| password | String | User Password (Password is hashed) |
| Field Name | Data Type | Description |
| --------------- | --------- | ---------------------------------- |
| id | Integer | Primary Key |
| username | String | User Name |
| email | String | User Email |
| password | String | User Password (Password is hashed) |
| friends | ManyToMany| Friends of the user |
| avatar | Image | User Avatar |
| otp_status | Boolean | OTP Status |
| otp | Integer | OTP |
| otp_expiry_time | DateTime | OTP Expiry Time |

Later I will limit the access to the API using nginx reverse proxy and only the frontend will be able to access the API.

Expand All @@ -93,6 +111,15 @@ This document provides an overview of how WebSocket integration has been impleme

### Backend Setup

## GameRoom model
The GameRoom model is used to store the game room information. The GameRoom model consists of the following fields:

| Field Name | Data Type | Description |
| ---------- | --------------------------- | ----------- |
| room_name | String | Room Name |
| player1 | ForeignKey from UserProfile | Player 1 |
| player2 | ForeignKey from UserProfile | Player 2 |

#### Dependencies

Django Channels and Redis were installed:
Expand Down Expand Up @@ -289,3 +316,5 @@ To integrate WebSocket connections in the frontend, follow these steps:
### Summary

The setup included configuring Django Channels, Redis, and Nginx to support WebSocket connections. The frontend can connect to the WebSocket service and handle events to provide real-time updates to users.


1 change: 0 additions & 1 deletion Backend/user_service/user_service/user_app/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
default_app_config = 'user_app.apps.UserAppConfig'
3 changes: 3 additions & 0 deletions Backend/user_service/user_service/user_app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ class UserProfileModel(AbstractUser):
avatar = models.ImageField(upload_to=user_directory_path, null=True, blank=True, default='default.jpg')
friends = models.ManyToManyField("self", blank=True, symmetrical=True)
online_status = models.BooleanField(default=False)
otp_status = models.BooleanField(default=False, blank=True, null=True)
otp = models.IntegerField(blank=True, null=True)
otp_expiry_time = models.DateTimeField(blank=True, null=True)
REQUIRED_FIELDS = ["email"]

class FriendRequest(models.Model):
Expand Down
13 changes: 12 additions & 1 deletion Backend/user_service/user_service/user_app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,18 @@ class UserSerializer(serializers.ModelSerializer):

class Meta:
model = UserProfileModel
fields = ["id", "username", "email", "password", "avatar", "online_status", "friends"]
fields = [
"id",
"username",
"email",
"password",
"avatar",
"online_status",
"friends",
"otp_status",
"otp",
"otp_expiry_time"
]
extra_kwargs = {"password": {"write_only": True}}

### Password should be strong password, minimum 8 characters, at least one uppercase letter, one lowercase letter, one number and one special character
Expand Down
Loading

0 comments on commit 5830e41

Please sign in to comment.