Skip to content

Свій адмінський клієнт ​

Друга половина протоколу: керування ефіром. Ця сторінка описує наскрізний шлях від входу до керування чергою.

Передбачається, що ви вже прочитали «Свій клієнт за 15 хвилин» — адмінське з'єднання будується поверх звичайного слухацького.

Дві половини автентифікації ​

Найчастіша причина «нічого не працює, і помилок теж немає»: адмін автентифікується двічі й окремо.

КаналЯкЩо дає
HTTPPOST /api/admin/login → кука або bearerдоступ до /api/admin/*
Socket.ioподія admin_activeадмінські події та їх результати

Успішний вхід через HTTP не робить ваш сокет адмінським. Поки не надіслано admin_active, сервер вважає з'єднання звичайним слухачем.

Крок 1. Вхід ​

http
POST /api/admin/login
Content-Type: application/json

{ "login": "admin", "password": "…" }
json
{ "ok": true }              // у продакшені
{ "ok": true, "token": "…" } // у режимі розробки

У продакшені токена в тілі відповіді немає

Він приходить лише в httpOnly-куці adminToken, яку JavaScript прочитати не може. Тому запити треба слати з credentials: 'include', а походження вашого клієнта — додати в CLIENT_ORIGIN на сервері. Токен живе 12 годин, кількість спроб входу обмежена.

Крок 2. Підвищення сокета ​

js
socket.emit('admin_active');            // токен візьметься з куки
socket.emit('admin_active', jwtToken);  // або передайте явно

socket.on('admin_confirmed', ({ role, privileges, authorized }) => {
  // будуйте інтерфейс саме за цим списком
});

socket.on('admin_error', (raw) => {
  const msg = JSON.parse(raw); // об’єкт з усіма локалями
});

Помилки тут мовчазні

Якщо сокет не підвищено, адмінські події ігноруються без відповіді — ні помилки, ні колбека. Клієнт виглядатиме «завислим». Завжди дочекайтеся admin_confirmed, перш ніж показувати керування.

authorized: false означає помічника, який ще не активував себе тимчасовим паролем: до активації він не може нічого.

Крок 3. Інтерфейс за привілеями ​

privileges — це масив рядків, і він авторитетніший за вміст токена: сервер перечитує права з бази при кожному admin_active, тому зміни діють без повторного входу.

js
const can = (p) => privileges.includes(p);
if (can('queue_manage')) renderQueueControls();

Повний перелік — у довіднику привілеїв. Супер-адмін отримує всі 13.

Права в інтерфейсі — це зручність, а не захист

Сервер перевіряє привілей на кожній операції незалежно. Ховати кнопки корисно, але покладатися на це не можна — і не потрібно.

Крок 4. Черга ​

js
socket.emit('get_queue', { offset: 0, limit: 20 }, ({ items, total }) => {
  // items: [{ id, title, artist, orderType }]
});

Читання йде через колбеки, а зміни — ні. Результат будь-якої зміни приходить окремою подією:

js
socket.on('admin_success', (raw) => showToast(pickLocalized(JSON.parse(raw))));
socket.on('admin_error',   (raw) => showError(pickLocalized(JSON.parse(raw))));

Зверніть увагу: payload — рядок, який треба спершу JSON.parse, і лише потім вибрати мову. Успішні повідомлення часто мають поле code (QUEUE_SONG_ADDED тощо) — гілкуйтеся по ньому, а не по тексту.

Операції над чергою:

js
socket.emit('admin_add_song', { id, title, artist, orderType: 'lastinline' });
socket.emit('admin_remove_song', position);  // зсув від поточного треку
socket.emit('admin_skip_song');
socket.emit('admin_insert_song_group', { groupId });

orderType буває 'lastinline' (звичайне) або 'donated' — друге ставить трек попереду звичайних замовлень.

Кулдауни — це нормальна відповідь, а не збій

Додавання, видалення й перемикання мають окремі затримки. Сервер повертає помилку з кількістю секунд очікування — покажіть її користувачеві замість того, щоб повторювати запит.

Позицію для admin_remove_song беріть із search_queue: там кожен елемент уже має поле position.

Крок 5. Замовлення слухачів ​

Адміни з queue_manage отримують список замовлень і оновлення до нього:

js
socket.on('suggestions_update', (list) => {
  // [{ uid, song: { id, title, artist }, addedAt }]
});

socket.emit('admin_suggestion_action', { uid, action: 'add' }); // або 'reject'

Замовлення живе 5 хвилин і згасає само — слухач у цьому разі отримає suggestion_result з auto: true. Якщо ухвалити вже згасле замовлення, повернеться помилка «не знайдено», тож список варто оновлювати за подією, а не кешувати надовго.

Одна сесія на обліковий запис ​

Коли той самий адмін підключається знову, попереднє з'єднання сервер прибирає. Друга вкладка тихо «відбирає» сесію в першої. Це очікувана поведінка, а не збій вашого клієнта.

Крок 6. Завантаження треку ​

Це не один запит, а конвеєр із трьох послідовних кроків — і трек не потрапляє в ефір, доки не пройдено останній. Потребує привілею upload_songs.

upload-check-duplicate  →  upload-song-file  →  upload-song-lyrics  →  upload-song-commit
      (необов’язково)          файл у сховище        текст пісні          запис у бібліотеку
js
// 1. Дешева перевірка до довгого завантаження
await post('/api/admin/upload-check-duplicate', { trackId: `day/${filename}` });

// 2. Файл — СИРИМИ БАЙТАМИ, не multipart
const { metadata } = await fetch(`${SERVER}/api/admin/upload-song-file?mode=day`, {
  method: 'POST',
  credentials: 'include',
  headers: {
    'Content-Type': 'audio/mpeg',
    'X-File-Name': encodeURIComponent(file.name),
  },
  body: file,          // Blob або ArrayBuffer
}).then((r) => r.json());

// 3. Текст пісні (необов’язково — LRCLIB, без облікових даних)
const { lyricsEntry } = await post('/api/admin/upload-song-lyrics', {
  title: metadata.title, artist: metadata.artist,
  album: metadata.album, duration: metadata.duration,
});

// 4. Лише тут трек з’являється в бібліотеці
await post('/api/admin/upload-song-commit', { metadata, lyricsEntry });

Файл надсилається сирими байтами

Другий крок читає тіло через express.raw, а не як multipart/form-data. FormData тут дасть 400. Ім’я файлу передається заголовком X-File-Name, бо в сирому тілі його нема звідки взяти. Обмеження — 80 МБ.

Між кроками 2 і 4 метадані можна показати користувачеві для правки — вони прочитані з ID3-тегів і часто потребують уточнення. Передавайте в commit уже виправлений об'єкт.

Після успішного commit сервер широкомовно шле library_updated — це сигнал усім клієнтам перечитати списки.

Що далі ​

Живий ефір — окрема тема з власним порядком дій: вихід в ефір, мікрофон, черга гостей, модерація. Розписана з діаграмами послідовностей у «Живий ефір: послідовності».

Решта адмінського контуру — редактор треків, тексти, налаштування, групи, журнал аудиту та облікові записи — розібрана в «Решта адмінських сценаріїв». Точні тіла запитів і права доступу — у довіднику REST та довіднику подій.