← BACK TO PROJECTS
FULL-STACK AI 2026

Fink.io
AI Quiz Platform за ФИНКИ

A full-stack educational platform where ФИНКИ students upload study materials (PDF, DOCX, PPTX) and AI generates quality quizzes in Macedonian. Built with Django + Channels, React + Vite, and Google Gemini AI. Features real-time WebSocket chat, friends system with notifications, badges, leaderboards, and instructor analytics.

SCREENSHOTS

FEATURES

🤖

AI Quiz Generation

Upload PDF/DOCX/PPTX → Google Gemini extracts text and generates quality Macedonian quizzes with multiple-choice, multi-select, and essay questions.

💬

Real-time Chat

WebSocket chat between friends with typing indicators, online presence, and optimistic UI (no message duplication).

👥

Friends System

Facebook-style requests → accept/reject. Search users, view public profiles, see online status, with bell notifications for activity.

🏆

Badges & Leaderboards

14 badge types × 4 tiers (bronze/silver/gold/diamond). Streaks, points, rank within role. Auto-awarded via Django signals.

📊

Instructor Analytics

Per-quiz dashboard with average score, score distribution histogram, completion rate. Clickable stat cards switch between drafts/published/games tabs.

🎨

Light & Dark Themes

Toggle between 3 modes: Light / Dark / System. Glassmorphism design with CSS variables. Anti-flash on page load.

🔐

Role-based Access

5 roles: guest, student, instructor, moderator, admin. JWT auth with 24h access + 30d refresh tokens.

📚

170 Subjects Seeded

Pre-populated with all FINKI subjects across 4 years × 8 semesters, mandatory + elective groups. Personalized by student's current year.

ARCHITECTURE

CLIENT
React + Vite
Tailwind, Zustand, React Router
↓ HTTP / WebSocket ↓
SERVER
Django 5 + Daphne (ASGI)
REST API · WebSocket · JWT
DATA
PostgreSQL
Users · Quizzes · Materials · Chat
AI
Google Gemini
Multi-model fallback chain
FILES
Material Storage
PDF / DOCX / PPTX extraction

The backend serves both HTTP (Django REST Framework) and WebSocket (Channels + Daphne) on the same ASGI app. WebSocket connections use a JWT token in the query string, authenticated by a custom middleware. Presence (online/offline) is tracked in-memory with a thread-safe counter — multi-tab support per user.

TECH STACK

BACKEND

  • Python 3.12
  • Django 5.x
  • Django REST Framework 3.15
  • Django Channels 4.1+
  • Daphne (ASGI server) 4.1+
  • simplejwt 5.x
  • google-genai SDK 1.0+
  • pdfplumber / python-docx / python-pptx
  • django-cors-headers

FRONTEND

  • React 18.x
  • Vite 5.x
  • Tailwind CSS 3.x
  • Zustand 4.x
  • React Router 6.x
  • Axios
  • Lucide React (icons)
  • Native WebSocket API

INFRASTRUCTURE

  • PostgreSQL 16 (production)
  • SQLite (development)
  • Render.com (hosting)
  • Google Gemini API (free tier)
  • Multi-model fallback: 2.5-flash-lite → 2.5-flash → 2.0-flash → 1.5-flash
  • In-memory channel layer (single instance)

DESIGN SYSTEM

  • Fraunces (display serif)
  • Manrope (UI sans)
  • JetBrains Mono (meta)
  • Accent: #ff5b1f (бренд оранж)
  • CSS variables for theming
  • Glassmorphism + backdrop-blur

API ENDPOINTS

AUTH

POST   /api/auth/token/                login
POST   /api/auth/token/refresh/        refresh
POST   /api/accounts/register/         register

ACCOUNTS & FRIENDS

GET    /api/accounts/me/                              мојот профил
PATCH  /api/accounts/me/                              уреди (multipart)
GET    /api/accounts/me/subjects/                     мои предмети
GET    /api/accounts/me/friend-requests/              pending барања
GET    /api/accounts/users/<id>/profile/              јавен профил
POST   /api/accounts/users/<id>/friend-request/       испрати
POST   /api/accounts/friend-requests/<id>/respond/    accept/reject
DELETE /api/accounts/users/<id>/friend/               отстрани
GET    /api/accounts/search/?q=...                    пребарувај

MATERIALS (БАЗИ)

GET    /api/materials/databases/               јавен каталог
POST   /api/materials/                          прикачи
POST   /api/materials/<id>/like/                like
POST   /api/materials/<id>/save/                bookmark
POST   /api/materials/<id>/download/            track
GET    /api/materials/saved/                    мои зачувани

QUIZZES

GET    /api/quizzes/                  листа + филтри
GET    /api/quizzes/mine/             мои
GET    /api/quizzes/saved/            зачувани
POST   /api/quizzes/create/           мануелно
POST   /api/quizzes/generate/         AI генерирај од material
POST   /api/quizzes/<id>/publish/
POST   /api/quizzes/<id>/like/
POST   /api/quizzes/<id>/save/
GET    /api/quizzes/<id>/play/        играj
POST   /api/quizzes/<id>/submit/      одговори
GET    /api/quizzes/attempts/         моја историја

NOTIFICATIONS

GET    /api/notifications/             листа
GET    /api/notifications/count/       непрочитани
POST   /api/notifications/<id>/read/
POST   /api/notifications/read-all/

CHAT (REST + WS)

GET    /api/chat/conversations/                разговори
POST   /api/chat/conversations/start/          {user_id}
GET    /api/chat/conversations/<id>/messages/
POST   /api/chat/conversations/<id>/read/
GET    /api/chat/unread-count/
GET    /api/chat/online-friends/

WS:    wss://api/ws/chat/?token=<jwt>

ROLES & PERMISSIONS

Role Capabilities
guest View public quizzes (no login)
student Play quizzes, upload materials, create quizzes, chat, friends
instructor + Command dashboard, per-quiz analytics
moderator + Review reported content queue
admin + User management, role changes, platform-wide control

CHALLENGES SOLVED

WebSocket message duplication

Initially, sent messages appeared twice (optimistic UI + WS echo). Solved by giving each message a temp_id from the client; the server replies only to the sender with message_confirmed containing that temp_id, allowing replacement of the optimistic entry without dupes.

Gemini API 503 — model overload

The free tier of Gemini frequently returns "model overloaded" errors. Implemented a fallback chain that tries 4 models with 2 retries each (2s backoff): 2.5-flash-lite → 2.5-flash → 2.0-flash → 1.5-flash. Success rate jumped from ~40% to ~95%.

Online presence across multiple tabs

A user with 2 tabs open should stay online if they close one. Built a thread-safe in-memory counter that tracks active connections per user — only broadcast "offline" event when the count hits zero. Friends get notified via WS group messages.

Dark theme — hardcoded colors everywhere

67 hardcoded color classes (bg-cream, text-ink-900, etc.) made some pages invisible in dark mode. Migrated to semantic CSS variables (bg-bg, text-fg, text-muted) with a batch sed script across all 14 pages.

Render free tier sleep

Free tier instances spin down after 15min idle. WebSocket reconnects automatically with exponential backoff; users see a "Се поврзува…" indicator. First request after sleep takes 30-60s to wake up — acceptable for a student platform.

Try it. Break it. Tell me what to fix.