\documentclass[12pt,a4paper]{article}

% Polskie znaki i kodowanie
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[polish]{babel}

% Marginesy
\usepackage{geometry}
\geometry{a4paper, margin=2.5cm}

% Zawijanie tekstu i mikrotypografia
\usepackage{microtype}
\usepackage[hyphens]{url}
\usepackage{xurl}          % pozwala łamać URL-e w dowolnym miejscu
\emergencystretch=3em      % zapobiega overfull hbox w uzasadnionych przypadkach
% \setlength{\parfillskip}{0pt plus 1fil} % naturalny fill na końcu akapitu
\setlength{\headheight}{13.59999pt}

% Grafika, tabele i kolory
\usepackage{graphicx}
\usepackage{float}
\usepackage{xcolor}
\usepackage{booktabs}

% Drzewo katalogów
\usepackage{dirtree}

% Zaawansowane nagłówki i stopki
\usepackage{fancyhdr}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[L]{\small\textbf{Technologie Webowe}}
\fancyhead[R]{\small\textit{Discord Clone - Dokumentacja}}
\fancyfoot[C]{\thepage}
\renewcommand{\headrulewidth}{0.4pt}
\renewcommand{\footrulewidth}{0.4pt}

% Podświetlanie składni (Kod źródłowy)
\usepackage{listings}
\definecolor{codegreen}{rgb}{0,0.6,0}
\definecolor{codegray}{rgb}{0.5,0.5,0.5}
\definecolor{codepurple}{rgb}{0.58,0,0.82}
\definecolor{backcolour}{rgb}{0.95,0.95,0.92}
\lstdefinestyle{mystyle}{
    backgroundcolor=\color{backcolour},   
    commentstyle=\color{codegreen},
    keywordstyle=\color{magenta},
    numberstyle=\tiny\color{codegray},
    stringstyle=\color{codepurple},
    basicstyle=\ttfamily\footnotesize,
    breakatwhitespace=false,  % łam linie również w środku tokenu (nie tylko przy białych znakach)
    breaklines=true,          % włącz automatyczne zawijanie długich linii
    breakautoindent=true,     % zachowaj wcięcie po złamaniu
    postbreak=\mbox{\textcolor{codegray}{$\hookrightarrow$}\space}, % strzałka przy złamaniu
    columns=flexible,         % elastyczne kolumny zapobiegają overfull w listingach
    captionpos=b,                    
    keepspaces=true,                 
    numbers=left,                    
    numbersep=5pt,                  
    showspaces=false,                
    showstringspaces=false,
    showtabs=false,                  
    tabsize=2,
    extendedchars=true,
    inputencoding=utf8,
    literate={ą}{{\k{a}}}1
             {ć}{{\'c}}1
             {ę}{{\k{e}}}1
             {ł}{{\l{}}}1
             {ń}{{\'n}}1
             {ó}{{\'o}}1
             {ś}{{\'s}}1
             {ź}{{\'z}}1
             {ż}{{\.z}}1
             {Ą}{{\k{A}}}1
             {Ć}{{\'C}}1
             {Ę}{{\k{E}}}1
             {Ł}{{\L{}}}1
             {Ń}{{\'N}}1
             {Ó}{{\'O}}1
             {Ś}{{\'S}}1
             {Ź}{{\'Z}}1
             {Ż}{{\.Z}}1
}
\lstset{style=mystyle}

% Konfiguracja linków
\usepackage{hyperref}
\hypersetup{
    colorlinks=true,
    linkcolor=blue!70!black,
    filecolor=magenta,
    urlcolor=blue!70!black,
    pdftitle={Discord Clone - Dokumentacja},
    pdfpagemode=FullScreen,
    breaklinks=true,   % pozwól na zawijanie linków
}

\begin{document}

% Globalne ustawienie luźniejszego składania tekstu – zapobiega overfull hbox
\sloppy

\begin{titlepage}
    \centering
    \vspace*{3cm}
    
    {\scshape\Large Przedmiot: Aplikacje internetowe w Django \par}
    \vspace{1.5cm}
    
    {\huge\bfseries Dokumentacja Projektowa\par}
    \vspace{0.5cm}
    {\Large\itshape Discord Clone: Aplikacja do komunikacji w czasie rzeczywistym\par}
    \vspace{2cm}
    
    \vfill
    
    {\large \today\par}
\end{titlepage}

\newpage
\tableofcontents
\newpage

\section{Wstęp i cel projektu}
Prezentowany projekt \textbf{,,Discord Clone''} to nowoczesna aplikacja webowa stworzona w celu dostarczenia funkcjonalności umożliwiających wieloosobową komunikację tekstową w czasie rzeczywistym, analogiczną do popularnej platformy Discord.

Głównym celem edukacyjnym i technologicznym projektu jest zintegrowanie klasycznego frameworka opartego na protokole HTTP z zaawansowanymi mechanizmami asynchronicznymi i protokołem \textbf{WebSocket}, z zachowaniem optymalnej wydajności i uproszczonej infrastruktury.

\subsection{Kluczowe funkcjonalności}
\begin{itemize}
    \item \textbf{Autoryzacja:} Rejestracja i logowanie. Dostęp do głównej funkcjonalności jest chroniony.
    \item \textbf{Zarządzanie Kanałami:} Użytkownicy z odpowiednimi uprawnieniami mogą tworzyć tematyczne kanały grupowe (tekstowe i głosowe).
    \item \textbf{Role i uprawnienia:} Podział na Administratorów, Moderatorów oraz Użytkowników z dedykowanymi przywilejami (np. usuwanie obcych wiadomości przez moderatora).
    \item \textbf{Czat Real-Time:} Natychmiastowe przesyłanie wiadomości bez konieczności odświeżania okna przeglądarki.
    \item \textbf{Kanały Głosowe (Voice Channels):} Wieloużytkownikowe rozmowy audio w czasie rzeczywistym realizowane w architekturze peer-to-peer (P2P) z wykorzystaniem WebRTC, wspierane przez globalną usługe Cloudflare Realtime TURN do NAT-traversalu.
\end{itemize}

\newpage
\section{Architektura systemu i wykorzystane technologie}
Aplikacja została zaprojektowana w oparciu o architekturę trójwarstwową, w której wyeliminowano konieczność stosowania osobnego serwera kolejkowego (np. Redis) na rzecz wbudowanych mechanizmów bazy PostgreSQL.

\subsection{Warstwa Prezentacji (Klient / Frontend)}
Użytkownik komunikuje się z systemem za pośrednictwem przeglądarki internetowej. Warstwa ta renderowana jest przy pomocy \textbf{szablonów HTML/CSS (Bootstrap)}. Lekki skrypt \textbf{JavaScript} nawiązuje i utrzymuje ciągłe połączenie WebSocket z serwerem, asynchronicznie odbierając powiadomienia o nowych wiadomościach. W przypadku połączeń głosowych, frontend odpowiada za przechwytywanie dźwięku z mikrofonu (Web Audio API), realizację nawiązania połączeń WebRTC P2P (Full Mesh) oraz dynamiczną obsługę odtwarzania strumieni audio od innych użytkowników bez przerywania nawigacji dzięki technologii AJAX/Fetch (SPA Shell).

\subsection{Warstwa Aplikacji (Backend)}
Główny trzon projektu oparty jest o język \textbf{Python} oraz framework \textbf{Django 4.2}. 
Ruch sieciowy jest zarządzany przez serwer \textbf{Daphne (ASGI)}, który:
\begin{itemize}
    \item \textbf{Zwykłe żądania HTTP} (np. logowanie, ładowanie widoków) przekazuje do synchronicznych widoków Django.
    \item \textbf{Żądania WebSocket} routuje do asynchronicznych consumerów zdefiniowanych w \textbf{Django Channels}.
\end{itemize}
W kontekście kanałów głosowych backend pełni funkcję serwera sygnalizacyjnego (signaling server) za pośrednictwem protokołu WebSocket, pośrednicząc w wymianie ofert SDP (Session Description Protocol) i kandydatów ICE między klientami. Dodatkowo, backend komunikuje się z interfejsem API Cloudflare, pobierając krótkotrwałe, bezpieczne dane uwierzytelniające dla serwerów TURN (ważne przez 24 godziny).

\subsection{Warstwa Danych}
Zastosowano zunifikowaną bazę danych \textbf{PostgreSQL}. Standardowe dane (użytkownicy, historia wiadomości) są obsługiwane przez mechanizm Django ORM. Dodatkowo, dzięki zastosowaniu pluginu \texttt{channels-postgres}, architektura w czasie rzeczywistym (pub/sub) wykorzystuje natywne zapytania \texttt{LISTEN/NOTIFY} z PostgreSQL, co pozwala rozgłaszać eventy WebSockets pomiędzy klientami. W bazie danych PostgreSQL przechowywany jest również stan aktywności użytkowników w kanałach głosowych za pomocą pola \texttt{current\_voice\_channel} w profilu użytkownika (\texttt{UserProfile}). Umożliwia to natychmiastowe renderowanie listy uczestników kanału głosowego bezpośrednio pod jego nazwą na liście kanałów podczas ładowania strony.

\newpage
\section{Środowisko i instalacja}

Do uruchomienia projektu w celach deweloperskich i testowych zastosowano konteneryzację, co eliminuje problem zależności systemowych.

\subsection{Wymagania systemowe}
\begin{itemize}
    \item \textbf{Docker} (Docker Engine)
    \item \textbf{Docker Compose}
    \item Połączenie z internetem (do pobrania obrazów oraz weryfikacji API Cloudflare)
\end{itemize}

\subsection{Kroki instalacji}
\begin{enumerate}
    \item \textbf{Pobranie kodu:} Sklonuj lub pobierz i rozpakuj repozytorium projektu w wybranym folderze.
    \item \textbf{Konfiguracja zmiennych środowiskowych:} Utwórz plik \texttt{.env} w katalogu głównym projektu na wzór udostępnionych szablonów. Wprowadź tam wygenerowane w panelu Cloudflare dane uwierzytelniające dla usługi Cloudflare Realtime TURN: \texttt{CLOUDFLARE\_ACCOUNT\_ID}, \texttt{CLOUDFLARE\_API\_TOKEN} oraz \texttt{CLOUDFLARE\_TURN\_KEY\_ID}. Umożliwi to serwerowi dynamiczne generowanie tokenów ICE dla przeglądarek.
    \item \textbf{Budowanie kontenerów:} Uruchom terminal w głównym katalogu (tam, gdzie znajduje się plik \texttt{docker-compose.yml}) i wykonaj polecenie:
\begin{lstlisting}[language=bash, style=mystyle, numbers=none]
docker-compose up --build
\end{lstlisting}
    \item \textbf{Uruchomienie bazy:} Kontenery wykonają automatycznie proces migracji struktury danych (\texttt{manage.py migrate}). Zostanie także wygenerowane konto administratora.
    \item Aplikacja docelowo jest dostępna pod adresem: \url{http://localhost:8000}.
\end{enumerate}

\textbf{Domyślne dane dostępowe administratora:}
\begin{itemize}
    \item Login: \texttt{admin}
    \item Hasło: \texttt{admin}
\end{itemize}

\newpage
\section{Struktura repozytorium}
Projekt zorganizowany jest wokół aplikacji modułowych typowych dla frameworka Django. Poniższe drzewo prezentuje kluczowe pliki z punktu widzenia architektonicznego.

\vspace{0.5cm}
% Szerokość minipage dopasowana do pozostałej przestrzeni kolumny dirtree
\dirtree{%
.1 / (Katalog głowny projektu).
.2 core/.
.3 settings.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Konfiguracja główna (baza danych, channels)\end{minipage}.
.3 asgi.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Punkt wejścia dla serwera Daphne i routing Channels\end{minipage}.
.3 urls.py \dots\dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Główny routing HTTP\end{minipage}.
.2 chat/.
.3 models.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje tabel DB (Użytkownicy, Kanały, Wiadomości)\end{minipage}.
.3 consumers.py \dots\dots\begin{minipage}[t]{6cm}\raggedright Asynchroniczna obsługa WebSocketów (czat, sygnalizacja WebRTC)\end{minipage}.
.3 routing.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Definicje ścieżek URL dla połączeń ws://\end{minipage}.
.3 views.py \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Widoki synchroniczne oraz endpoint generowania tokenów TURN\end{minipage}.
.2 docker-compose.yml \dots\dots\begin{minipage}[t]{6cm}\raggedright Orkiestracja kontenerów (PostgreSQL + aplikacja)\end{minipage}.
.2 Dockerfile \dots\dots\dots\dots\begin{minipage}[t]{6cm}\raggedright Instrukcja budowania środowiska bazowego Python\end{minipage}.
.2 requirements.txt \dots\dots\begin{minipage}[t]{6cm}\raggedright Zależności (Django, channels-postgres)\end{minipage}.
}

\newpage
\section{Kluczowe fragmenty implementacji}
W tej sekcji przedstawiono wybrane fragmenty kodu źródłowego odpowiadające za najważniejsze mechanizmy realizujące komunikację w czasie rzeczywistym.

\subsection{Consumer obsługujący WebSocket (chat/consumers.py)}
Poniższy kod ukazuje w jaki sposób framework Channels obsługuje podłączanie się użytkowników do specyficznej dla kanału ,,grupy'' oraz metodykę odbierania wiadomości tekstowych z przeglądarki i rozsyłania ich w czasie rzeczywistym.

\begin{lstlisting}[language=Python, caption={Przykład implementacji klasy \texttt{AsyncWebsocketConsumer}}]
import json
from channels.generic.websocket import AsyncWebsocketConsumer

class ChatConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.room_name = self.scope['url_route']['kwargs']['room_name']
        self.room_group_name = f"chat_{self.room_name}"

        # Dodanie klienta do grupy nasluchujacej
        await self.channel_layer.group_add(
            self.room_group_name,
            self.channel_name
        )
        await self.accept()

    async def disconnect(self, close_code):
        # Odlaczenie od grupy przy zamknieciu strony
        await self.channel_layer.group_discard(
            self.room_group_name,
            self.channel_name
        )

    async def receive(self, text_data):
        text_data_json = json.loads(text_data)
        message = text_data_json['message']

        # Rozeslanie otrzymanej wiadomosci do calej grupy
        await self.channel_layer.group_send(
            self.room_group_name,
            {
                'type': 'chat_message',
                'message': message
            }
        )

    # Funkcja wywolywana, gdy warstwa kanałów wysyla typ 'chat_message'
    async def chat_message(self, event):
        message = event['message']
        await self.send(text_data=json.dumps({
            'message': message
        }))
\end{lstlisting}

\subsection{Generowanie tokenów TURN (chat/views.py)}
Poniższy kod prezentuje bezpieczne pobieranie danych uwierzytelniających (tokenów ICE) z API Cloudflare TURN za pomocą synchronicznego widoku Django. W przypadku braku konfiguracji kluczy Cloudflare zastosowano automatyczną ścieżkę awaryjną (fallback) do publicznych serwerów STUN.

\begin{lstlisting}[language=Python, caption={Pobieranie tokenów ICE Cloudflare Realtime TURN}]
import urllib.request
import urllib.error
import json
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required

@login_required
def voice_credentials_view(request):
    api_token = getattr(settings, 'CLOUDFLARE_API_TOKEN', '')
    turn_key_id = getattr(settings, 'CLOUDFLARE_TURN_KEY_ID', '')

    if not api_token or not turn_key_id:
        return JsonResponse({
            "iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
        })

    url = (
        f"https://rtc.live.cloudflare.com/v1/turn/keys/"
        f"{turn_key_id}/credentials/generate-ice-servers"
    )
    headers = {
        "Authorization": f"Bearer {api_token}",
        "Content-Type": "application/json"
    }
    body = json.dumps({"ttl": 86400}).encode("utf-8")
    req = urllib.request.Request(url, data=body, headers=headers, method="POST")
    try:
        with urllib.request.urlopen(req, timeout=5) as response:
            return JsonResponse(json.loads(response.read().decode()))
    except urllib.error.URLError:
        return JsonResponse({
            "iceServers": [{"urls": ["stun:stun.cloudflare.com:3478"]}]
        })
\end{lstlisting}

\subsection{Sygnalizacja WebRTC (chat/consumers.py)}
Aby nawiązać połączenie P2P, klienci muszą wymienić oferty SDP i kandydatów ICE. Poniższy fragment klasy \texttt{VoiceSignalingConsumer} realizuje to zadanie, automatycznie aktualizując status obecności użytkownika w kanale głosowym w bazie danych oraz rozsyłając sygnały do właściwych odbiorców docelowych.

\begin{lstlisting}[language=Python, caption={Klasa \texttt{VoiceSignalingConsumer} obsługująca sygnalizację WebRTC}]
from channels.generic.websocket import AsyncWebsocketConsumer

class VoiceSignalingConsumer(AsyncWebsocketConsumer):
    async def connect(self):
        self.channel_id = self.scope['url_route']['kwargs']['channel_id']
        self.group_name = f'voice_{self.channel_id}'
        self.user = self.scope['user']

        await self.channel_layer.group_add(self.group_name, self.channel_name)
        await self.accept()

        await self.set_voice_channel(self.channel_id)
        await self.channel_layer.group_send(
            'global_updates',
            {
                'type': 'voice_presence_change',
                'user_id': self.user.id,
                'username': self.user.username,
                'channel_id': int(self.channel_id),
                'action': 'joined',
            }
        )

    async def receive(self, text_data):
        data = json.loads(text_data)
        if data.get('action') == 'signal':
            await self.channel_layer.group_send(
                self.group_name,
                {
                    'type': 'voice_signal_relay',
                    'sender_id': self.user.id,
                    'target_id': data.get('target_id'),
                    'data': data.get('data'),
                }
            )

    async def voice_signal_relay(self, event):
        if event['target_id'] == self.user.id:
            await self.send(text_data=json.dumps(event))
\end{lstlisting}

\newpage
\section{Prezentacja interfejsu graficznego}

Kolejne ekrany prezentują wygląd aplikacji z perspektywy klienta webowego. Widoki zostały przygotowane w oparciu o framework Bootstrap, co zapewnia responsywność interfejsu na różnych urządzeniach.

\subsection{Dostęp i Autoryzacja}

\begin{figure}[H]
    \centering
    % Odkomentuj poniższą linię, aby wstawić prawdziwy plik graficzny
    \includegraphics[width=0.7\textwidth]{images/login_screen.png}
    \caption{Ekran logowania -- Wymagany dla wszystkich użytkowników przed przystąpieniem do konwersacji.}
    \label{fig:login}
\end{figure}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.7\textwidth]{images/register_screen.png}
    \caption{Ekran rejestracji -- Umożliwia utworzenie nowego profilu.}
    \label{fig:register}
\end{figure}

\subsection{Główny panel komunikacyjny}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.7\textwidth]{images/chat_interface.png}
    \caption{Główne okno aplikacji przypominające interfejs platformy Discord. Po lewej stronie widoczna jest lista dostępnych kanałów tematycznych, z prawej okno wybranego czatu z historią wiadomości.}
    \label{fig:main_chat}
\end{figure}

\begin{figure}[H]
    \centering
    \includegraphics[width=0.7\textwidth]{images/admin_panel.png}
    \caption{Panel Administracyjny Uprawnień -- Służy do moderowania i edycji uprawnień użytkowników.}
    \label{fig:admin_panel}
\end{figure}

\newpage
\section{Podsumowanie}
Projekt Discord Clone stanowi kompletną, chociaż z konieczności uproszczoną na potrzeby ćwiczeniowe, wersję nowoczesnego komunikatora grupowego. Użycie architektury ASGI (Daphne, Django Channels) przełamuje ograniczenia tradycyjnego, synchronicznego modelu HTTP/WSGI i pozwala na obsługę setek równoczesnych połączeń w czasie rzeczywistym.

\vspace{0.3cm}
\textbf{Najważniejsze osiągnięcia technologiczne zastosowane w projekcie:}
\begin{enumerate}
    \item \textbf{Integracja \texttt{channels-postgres}:} Rezygnacja z klasycznego wykorzystywania zewnętrznych rozwiązań cachingowych (takich jak np. Redis) pozwala znacznie uprościć infrastrukturę serwerową oraz obniżyć zasoby wykorzystywane przez kontenery Dockera. Komunikaty WebSocket skutecznie wykorzystują wewnętrzne wyzwalacze PostgreSQL.
    \item \textbf{Podział uprawnień:} Prawidłowe zdefiniowanie ról wewnątrz systemu uniemożliwia użytkownikom ze standardowymi uprawnieniami modyfikowanie i kasowanie wiadomości innych użytkowników, chroniąc system przed nadużyciami.
    \item \textbf{Prostota środowiska deweloperskiego:} Plik konfiguracyjny \texttt{docker-compose.yml} sprowadza proces instalacji środowiska uruchomieniowego do jednej komendy terminala, rozwiązując odwieczny problem niezgodności wersji paczek pomiędzy maszynami programistów.
    \item \textbf{Komunikacja głosowa WebRTC z Cloudflare Realtime TURN:} Zaimplementowanie kanałów głosowych w modelu P2P Full Mesh, przy jednoczesnym wyeliminowaniu kosztów stałych dzięki darmowemu pakietowi Cloudflare Realtime (do 1000 GB egressu/miesiąc) i dynamicznemu generowaniu krótkoterminowych tokenów sesyjnych.
\end{enumerate}
Projekt daje doskonałą podstawę do wdrażania kolejnych funkcji w przyszłości, takich jak udostępnianie ekranu (screen sharing), grupowe rozmowy wideo czy natywne powiadomienia typu Push Notifications.

\end{document}