Вивчай
Домашнє завдання #17 ·
балівintermediate

Система ролей для блог-платформи

Уяви: ти створюєш блог-платформу, де різні люди мають різні права. Автор може писати чернетки, але не може публікувати їх сам. Модератор перевіряє та публікує контент. Адмін може все.

Така система дозволів (permissions) — основа будь-якого реального проєкту: від WordPress до GitHub. Ти побудуєш її з нуля, використовуючи класи та наслідування.


Файлова структура

blog-roles/
├── index.html      ← мінімальна сторінка (підключає script.js)
└── script.js       ← весь код тут

Як працюють дозволи

Вся система побудована на одному простому принципі: кожна роль має набір дозволів (permissions), а перевірка — це один метод hasPermission(action).

Ось які дозволи існують у нашій платформі:

ДозвілЩо дозволяє
create:draftСтворити чернетку поста
edit:own-draftРедагувати свою чернетку
delete:own-draftВидалити свою чернетку
publish:postОпублікувати чернетку (зробити її видимою)
unpublish:postЗняти пост з публікації
edit:any-postРедагувати будь-який пост
delete:any-postВидалити будь-який пост
manage:usersКерувати користувачами

Частина 1: Базовий клас User

Кожен зареєстрований користувач — це User. Створи клас, який:

  • Приймає options-об'єкт { name, email, age } при створенні
  • Валідує дані в конструкторі — кидає Error, якщо:
    • Ім'я коротше за 2 символи
    • Email не містить @
    • Вік не в діапазоні 16–120
  • Зберігає свій набір дозволів — для звичайного User це: create:draft, edit:own-draft, delete:own-draft
  • Має метод hasPermission(action) — повертає true, якщо дозвіл є в наборі
  • Має getter role, який повертає "user"
  • Має getter info, який повертає рядок: "Ім'я (email) [role]"
  • Має метод greet(), який повертає привітання з іменем

Як це має працювати:

const user = new User({ name: "Олексій", email: "alex@blog.com", age: 25 });

console.log(user.info);    // "Олексій (alex@blog.com) [user]"
console.log(user.greet()); // "Привіт, я Олексій!"

// Автор може створювати чернетки, але не може публікувати
console.log(user.hasPermission("create:draft"));  // true
console.log(user.hasPermission("edit:own-draft")); // true
console.log(user.hasPermission("publish:post"));   // false
console.log(user.hasPermission("manage:users"));   // false

// Валідація
new User({ name: "", email: "test@mail.com", age: 25 });
// Error: ім'я занадто коротке

Частина 2: Клас Moderator

Модератор — це User з додатковими правами на публікацію контенту. Створи клас Moderator, який:

  • Наслідує User
  • Має всі дозволи User плюс: publish:post, unpublish:post, edit:any-post
  • Перевизначає getter role — повертає "moderator"

Зверни увагу: hasPermission не потрібно переписувати — він працює однаково для всіх ролей, просто набір дозволів більший.

Як це має працювати:

const mod = new Moderator({ name: "Марія", email: "maria@blog.com", age: 30 });

console.log(mod.info); // "Марія (maria@blog.com) [moderator]"

// Модератор може все, що й User...
console.log(mod.hasPermission("create:draft"));  // true
console.log(mod.hasPermission("edit:own-draft")); // true

// ...плюс публікація та редагування чужих постів
console.log(mod.hasPermission("publish:post"));  // true
console.log(mod.hasPermission("edit:any-post")); // true

// Але не може керувати користувачами
console.log(mod.hasPermission("manage:users")); // false

Частина 3: Клас Admin

Адмін може все. Створи клас Admin, який:

  • Наслідує User (або Moderator — подумай, що логічніше)
  • hasPermission() завжди повертає true — адмін має доступ до всього
  • Перевизначає getter role — повертає "admin"

Як це має працювати:

const admin = new Admin({ name: "Катерина", email: "kate@blog.com", age: 28 });

console.log(admin.info); // "Катерина (kate@blog.com) [admin]"

// Адмін може все
console.log(admin.hasPermission("publish:post"));  // true
console.log(admin.hasPermission("manage:users"));   // true
console.log(admin.hasPermission("delete:any-post")); // true

// Навіть дозволи, яких немає в таблиці — адмін все одно може
console.log(admin.hasPermission("launch:rockets")); // true

Частина 4: Клас UserManager

Тепер потрібен менеджер, який керує всіма користувачами платформи. Створи клас UserManager, який:

  • Зберігає список користувачів у приватному полі #users
  • addUser(user) — додати користувача. Якщо email вже існує — кинути помилку
  • removeUser(email) — видалити за email
  • findByEmail(email) — знайти користувача
  • getByRole(role) — повернути масив користувачів з конкретною роллю ("user", "moderator", "admin")
  • getAllInfo() — масив рядків info всіх користувачів
  • getter count — кількість користувачів
  • whoCanPublish() — повернути масив тих, хто має дозвіл publish:post

Як це має працювати:

const manager = new UserManager();

manager.addUser(new User({ name: "Олексій", email: "alex@blog.com", age: 25 }));
manager.addUser(new Moderator({ name: "Марія", email: "maria@blog.com", age: 30 }));
manager.addUser(new Admin({ name: "Катерина", email: "kate@blog.com", age: 28 }));

console.log(manager.count); // 3

// Хто може публікувати пости?
const publishers = manager.whoCanPublish();
console.log(publishers.length); // 2 (модератор + адмін)

// Фільтр за роллю
console.log(manager.getByRole("moderator").length); // 1

// Дублікат email
manager.addUser(new User({ name: "Інший", email: "alex@blog.com", age: 20 }));
// Error: користувач з таким email вже існує

console.log(manager.getAllInfo());
// ["Олексій (alex@blog.com) [user]",
//  "Марія (maria@blog.com) [moderator]",
//  "Катерина (kate@blog.com) [admin]"]

Фінальна перевірка

Напиши в кінці script.js код, який створює команду блог-платформи та виводить у консоль звіт:

=== Блог-платформа: команда ===
Всього користувачів: 5
Адміни: Катерина
Модератори: Марія, Сергій
Автори: Олексій, Дмитро

Хто може публікувати: Марія, Сергій, Катерина
Хто може керувати користувачами: Катерина

Бонус

  • Статичний метод User.fromObject(obj) — створює потрібний клас (User, Moderator або Admin) залежно від поля role в об'єкті. Корисно для завантаження даних з API
  • Метод sortByAge() у UserManager — повертає копію масиву, відсортовану за віком. Оригінальний масив не змінюється
  • Перевірка дозволу перед дією: напиши функцію tryAction(user, action), яка перевіряє дозвіл і повертає "Ім'я виконав action" або "Ім'я: доступ заборонено для action"

Підказки

  • Дозволи зручно зберігати як масив рядків у класі. В конструкторі дочірнього класу додай свої дозволи до батьківських через spread: [...super_permissions, "publish:post"]
  • super(options) у конструкторі дочірнього класу передає весь об'єкт батьківському — він візьме звідти те, що йому потрібно
  • Для Admin не потрібен масив дозволів — просто перевизнач hasPermission(), щоб завжди повертав true
  • Приватне поле #users = [] — до нього не можна дістатися ззовні класу
  • includes() — найпростіший спосіб перевірити, чи є рядок у масиві

Критерії оцінки

КритерійБали
Клас User: options-об'єкт, валідація, permissions, hasPermission, info, greet25
Клас Moderator: наслідування, розширені дозволи, role20
Клас Admin: наслідування, hasPermission завжди true, role15
Клас UserManager: приватне поле, addUser з перевіркою, getByRole, whoCanPublish25
Використання private fields, static, getters, наслідування15
Бонус: fromObject / sortByAge / tryAction+15