docs: add project documentation with screenshots

This commit is contained in:
2026-05-24 19:53:59 +02:00
parent 6767695639
commit 87eaa0be59
6 changed files with 399 additions and 0 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 549 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 562 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 KiB

BIN
View File
Binary file not shown.
+399
View File
@@ -0,0 +1,399 @@
\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}