Skip to content

Migrate Notifications server to python #198 #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion UPGRADING.rst
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ List of changes since the *CONFIG_VERSION* numbering was introduced:
* Added *NOTIFICATIONS_* options to *deployment/settings.py*::

# Notifications configuration (client)
# This one is for JavaScript socket.io client.
# This one is for JavaScript WebSocket client.
# It should contain actual URL available from remote machines.
NOTIFICATIONS_SERVER_URL = 'http://localhost:7887/'

Expand Down
2 changes: 1 addition & 1 deletion oioioi/default_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@
RANKING_MAX_COOLDOWN = 100 # seconds

# Notifications configuration (client)
# This one is for JavaScript socket.io client.
# This one is for JavaScript WebSocket client.
# It should contain actual URL available from remote machines.
NOTIFICATIONS_SERVER_URL = 'http://localhost:7887/'

Expand Down
2 changes: 1 addition & 1 deletion oioioi/deployment/settings.py.template
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,7 @@ ZEUS_INSTANCES = {
# RANKING_MAX_COOLDOWN = 100 # seconds

# Notifications configuration (client)
# This one is for JavaScript socket.io client.
# This one is for JavaScript WebSocket client.
# It should contain actual URL available from remote machines.
# NOTIFICATIONS_SERVER_URL = 'http://localhost:7887/'

Expand Down
8 changes: 3 additions & 5 deletions oioioi/notifications/README.rst
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
An optional module for transmitting instant notifications to users.
It consists of a Django app and a Node.js server passing notifications.
It consists of a Django app and a Python server passing notifications.
The server is tested with normal Django unit tests.

How to use:
- expose port 7887 for web container in docker-compose-dev.yml
- open container bash (easy_toolbox.py bash)
- install Node.js (sudo apt install nodejs npm)
- uncomment notifications from INSTALLED_APPS in settings.py
- uncomment notifications from context_processors in settings.py
- set following settings in settings.py:
NOTIFICATIONS_SERVER_ENABLED = True
NOTIFICATIONS_RABBITMQ_URL = 'amqp://oioioi:oioioi@broker'
NOTIFICATIONS_SERVER_URL = 'http://localhost:7887/'
- stop and start again the containers (easy_toolbox.py stop; easy_toolbox.py start)
- stop and start again the containers (easy_toolbox.py stop; easy_toolbox.py up)



Expand All @@ -23,8 +23,6 @@ If required, modify appropriate settings in settings.py file - their names begin
with "NOTIFICATIONS_" prefix.


How to run tests:
- in ./server directory, invoke: npm test

How to notify users manually:
- invoke: ./manage.py notify [options]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,17 @@
import os

from django.conf import settings
from django.core.management.base import BaseCommand
from oioioi.notifications.server.server import Server
import asyncio


class Command(BaseCommand):
help = "Runs the OIOIOI notifications server"
requires_model_validation = False

def add_arguments(self, parser):
parser.add_argument(
'-i',
'--install',
action='store_true',
help="install dependencies required by the server",
)

def handle(self, *args, **options):
path = os.path.join(os.path.dirname(__file__), '..', '..', 'server')
os.chdir(path)
if options['install']:
os.execlp('env', 'env', 'npm', 'install')
else:
os.execlp(
'env',
'env',
'node',
'ns-main.js',
'--port',
settings.NOTIFICATIONS_SERVER_PORT.__str__(),
'--url',
settings.NOTIFICATIONS_OIOIOI_URL,
'--amqp',
settings.NOTIFICATIONS_RABBITMQ_URL,
)
server = Server(settings.NOTIFICATIONS_SERVER_PORT,
settings.NOTIFICATIONS_RABBITMQ_URL, settings.NOTIFICATIONS_OIOIOI_URL)
try:
asyncio.run(server.run())
except KeyboardInterrupt:
pass # Allow graceful shutdown on Ctrl+C
1 change: 0 additions & 1 deletion oioioi/notifications/server/.gitignore

This file was deleted.

Empty file.
139 changes: 0 additions & 139 deletions oioioi/notifications/server/auth.js

This file was deleted.

55 changes: 55 additions & 0 deletions oioioi/notifications/server/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import aiohttp
import logging
from cachetools import TTLCache
from typing import Optional


class Auth:
AUTH_CACHE_EXPIRATION_SECONDS = 300
AUTH_CACHE_MAX_SIZE = 10000
URL_AUTHENTICATE_SUFFIX = 'notifications/authenticate/'

def __init__(self, url: str):
self.auth_url = url + self.URL_AUTHENTICATE_SUFFIX
self.auth_cache: TTLCache[str, str] = TTLCache(
maxsize=self.AUTH_CACHE_MAX_SIZE, ttl=self.AUTH_CACHE_EXPIRATION_SECONDS)
self.logger = logging.getLogger(__name__)
self.http_client: Optional[aiohttp.ClientSession] = None

async def connect(self):
self.http_client = aiohttp.ClientSession()

async def close(self):
if self.http_client is not None:
await self.http_client.close()
self.http_client = None
self.logger.info("HTTP client closed")

async def authenticate(self, session_id: str) -> str:
"""
Authenticate a user with session ID.

Returns the user ID if authentication is successful.
Raises RuntimeError if authentication fails.
"""
if self.http_client is None:
raise RuntimeError("Connection not established. Call connect() first.")

if session_id in self.auth_cache:
user_id = self.auth_cache[session_id]
self.logger.debug(f"Cache hit for session ID: {session_id} with user ID: {user_id}")
return user_id

async with self.http_client.post(
self.auth_url,
data={'nsid': session_id},
headers={'Content-Type': 'application/x-www-form-urlencoded'}
) as response:
response.raise_for_status()
result = await response.json()

user_id = result['user']
self.auth_cache[session_id] = user_id

self.logger.debug(f"Authenticated session ID: {session_id} with user ID: {user_id}")
return user_id
Loading