CampusRaid — гейміфікована освітня платформа для студентів та викладачів, де вивчення програмування перетворюється на битви з IT-монстрами (босами).
Архітектура побудована за принципом «швидко в розробці, легко деплоїться, безпечно типізується». Замість класичного стеку Node.js використовується Bun — неймовірно швидкий рантайм з вбудованим TypeScript.
| Технологія | Роль |
|---|---|
| Bun | JavaScript runtime (швидша альтернатива Node.js) |
| Express.js | HTTP-фреймворк для побудови REST API |
| Prisma ORM | Типобезпечна робота з базою даних |
| PostgreSQL | Основна база даних (NeonDB / Supabase / Railway) |
| Zod | Runtime-валідація тіл запитів |
| JWT (jsonwebtoken) | Аутентифікація через Bearer-токени |
| bcryptjs | Хешування паролів |
| CORS | Дозвіл запитів з PWA-фронтенду |
| Render.com | Хостинг сервера (автоматичний деплой) |
backend/
├── prisma/
│ ├── schema.prisma # Схема бази даних (всі моделі)
│ └── seed.ts # Наповнення БД початковими даними
├── src/
│ ├── controllers/
│ │ ├── auth.ts # Реєстрація, логін, вибір університету
│ │ ├── forum.ts # Пости, коментарі, голосування
│ │ ├── game.ts # Боси, атаки, пазли
│ │ ├── leaderboard.ts # Рейтинги студентів та університетів
│ │ ├── chat.ts # Приватні повідомлення між друзями
│ │ ├── friends.ts # Запити дружби та список друзів
│ │ ├── shop.ts # Магазин VIP-титулів
│ │ └── teacher.ts # Дашборд та кастомні боси для викладача
│ ├── middleware/
│ │ └── auth.ts # JWT-middleware (authenticateToken)
│ ├── routes/
│ │ └── api.ts # Центральний роутер всіх ендпоінтів
│ ├── lib/
│ │ └── prisma.ts # Singleton-інстанція Prisma Client
│ └── server.ts # Точка входу, Express app, CORS, порт
└── .env # Секрети (DATABASE_URL, JWT_SECRET)
1. Клонуй репозиторій та встановлюй залежності:
bun install2. Налаштуй середовище:
Створи файл .env:
DATABASE_URL="postgresql://user:password@host:5432/dbname?sslmode=require"
JWT_SECRET="your-super-secret-key-change-in-production"
PORT=30003. Синхронізуй схему БД:
bun run db:push4. Заповни БД початковими даними:
bun run prisma/seed.tsSeed створить: базовий університет, 5 VIP-предметів у магазині, та 3 унікальних босів (кожен з 4 рівнями пазлів різної складності).
5. Запуск сервера (dev-режим з автоперезавантаженням):
bun run devПісля успішного логіну або реєстрації сервер повертає { token, user }:
{
"token": "eyJhbGci...",
"user": { "id": "...", "name": "Влад", "email": "...", "xp": 150, "coins": 20 }
}Всі захищені маршрути потребують заголовка:
Authorization: Bearer <token>
JWT-middleware (src/middleware/auth.ts) верифікує токен та підставляє req.user з даними авторизованого користувача.
Базовий URL: /api. Всі тіла запитів — application/json.
Реєстрація нового користувача.
| Поле | Тип | Опис |
|---|---|---|
email |
string | Email користувача |
password |
string | Пароль (мін. 6 символів) |
name |
string | Ім'я/нікнейм |
Відповідь: { token, user }
Вхід в акаунт.
| Поле | Тип | Опис |
|---|---|---|
email |
string | Email користувача |
password |
string | Пароль |
Відповідь: { token, user }
Список усіх університетів для вибору при реєстрації.
Оновлення університету після реєстрації.
| Поле | Тип |
|---|---|
universityId |
string |
Список постів. Сортуються за рейтингом (upvotes − downvotes). Якщо universityId не вказано — повертає всі пости.
Створити новий пост.
| Поле | Тип |
|---|---|
title |
string |
content |
string |
universityId |
string |
Деталі поста з усіма коментарями та відповідями (nested).
Проголосувати за пост.
| Поле | Тип | Значення |
|---|---|---|
type |
string | "UP" або "DOWN" |
Логіка: Якщо вже голосував — змінює голос. Якщо голосував тим самим — скасовує голос.
Додати коментар або відповідь.
| Поле | Тип | Опис |
|---|---|---|
content |
string | Текст коментаря |
parentId |
string? | ID батьківського коментаря (якщо відповідь) |
Отримати поточного активного боса університету з перемішаними блоками коду для пазлу.
Відповідь включає:
- Дані боса (ім'я, HP, фото)
- Поточний пазл (
title,description,type) - Масив перемішаних блоків коду
Спроба вирішити пазл.
| Поле | Тип | Опис |
|---|---|---|
puzzleId |
string | ID пазлу |
blockIds |
string[] | Масив ID блоків у вибраному порядку |
Відповідь при успіху:
{ "success": true, "damage": 50, "message": "Критичний удар! +50 XP, +10 монет" }Логіка:
- Порівнює порядок
blockIdsз правильним порядком у БД - При успіху: знімає HP боса, нараховує XP та монети, перемикає пазл на наступний
- При смерті боса (HP ≤ 0): автоматично активує наступного боса
Приєднатися до приватного боса викладача за кодом запрошення (формат: HACK-XXX).
Топ-10 студентів за XP. Включає ім'я, XP, університет.
Рейтинг університетів за сумарним XP всіх студентів.
Топ гравців, що завдали найбільше шкоди конкретному босу.
Надіслати повідомлення. Дозволено тільки між друзями.
| Поле | Тип |
|---|---|
receiverId |
string |
content |
string |
Отримати повну історію переписки між авторизованим користувачем та user2Id.
Список усіх діалогів авторизованого користувача з останніми повідомленнями та кількістю непрочитаних.
Позначити всі повідомлення від партнера як прочитані.
| Поле | Тип |
|---|---|
partnerId |
string |
Надіслати запит у друзі.
| Поле | Тип |
|---|---|
user2Id |
string |
Прийняти або відхилити запит.
| Поле | Тип | Значення |
|---|---|---|
friendshipId |
string | ID заявки |
action |
string | "accept" або "reject" |
Список підтверджених друзів користувача.
Список усіх доступних VIP-титулів (назва, опис, ціна в монетах).
Купити предмет.
| Поле | Тип |
|---|---|
itemId |
string |
Логіка: Перевіряє баланс монет, унікальність покупки, списує монети, додає титул.
Екіпірувати титул (зробити активним, відображатиметься на форумі).
| Поле | Тип |
|---|---|
itemId |
string |
Аналітичний дашборд: кількість створених босів, відсоток успіху студентів, кількість унікальних учасників.
Створити кастомного боса з власними завданнями.
{
"name": "Тест з ООП",
"maxHp": 1000,
"universityId": "...",
"puzzles": [
{
"title": "Пазл 1",
"description": "Опис завдання",
"type": "ORDERING",
"blocks": [
{ "text": "class Animal {", "order": 1 },
{ "text": " speak() {}", "order": 2 }
]
}
]
}Відповідь включає inviteCode (формат HACK-XXX) для розповсюдження студентам.
User ─────────────────────────────────────────
id, name, email, password (bcrypt)
role: STUDENT | TEACHER | ADMIN
xp, coins
titles: string[] ← масив придбаних/екіпірованих VIP-титулів
universityId → University
University ───────────────────────────────────
id, name
Boss ─────────────────────────────────────────
id, name, imageUrl
maxHp, currentHp
status: ACTIVE | DEFEATED
universityId → University
inviteCode (для кастомних босів викладача)
creatorId → User (nullable)
Puzzle ────────────────────────────────────────
id, title, description
type: MULTIPLE_CHOICE | ORDERING
isActive, order
bossId → Boss
Block ─────────────────────────────────────────
id, text, order
puzzleId → Puzzle
BossAttempt ──────────────────────────────────
userId, puzzleId
success: boolean
damage: int
Post ──────────────────────────────────────────
id, title, content
userId → User
universityId → University
upvotes, downvotes
PostVote ─────────────────────────────────────
userId, postId
type: UP | DOWN
Comment ──────────────────────────────────────
id, content
userId → User
postId → Post
parentId → Comment (nullable, для відповідей)
Message ──────────────────────────────────────
id, content, isRead
senderId → User
receiverId → User
Friendship ────────────────────────────────────
id, status: PENDING | ACCEPTED | REJECTED
user1Id → User
user2Id → User
ShopItem ──────────────────────────────────────
id, name, description
price (монети)
type: TITLE
UserShopItem ─────────────────────────────────
userId → User
itemId → ShopItem
- Підключіть GitHub-репозиторій до render.com.
- Створіть Web Service з параметрами:
- Build Command:
bun install && bunx prisma generate && bun run db:push - Start Command:
bun src/server.ts
- Build Command:
- Додайте змінні середовища:
DATABASE_URL= рядок підключення PostgreSQLJWT_SECRET= довгий випадковий рядок
- Deploy.
💡 Після першого деплою виконайте seed через Render Shell:
bun run prisma/seed.ts
- Паролі хешуються через bcryptjs (cost factor 10)
- JWT-токени живуть 30 днів
- Всі маршрути (крім
/auth/login,/auth/register,/universities) захищені middleware req.user.idбереться виключно з JWT-токену — підробити не можна- Паролі ніколи не повертаються в API-відповідях (деструктуризація з видаленням поля)
- Zod-валідація на всіх вхідних даних — SQL injection неможливий через Prisma