🐝 beeu — Керівництво · Пайплайн людей v1.6 ← Мій кабінет
← До списку

Пайплайн людей v1.6 — ролі, реєстрація, доступ, схвалення даних

Версія: v1.6 · 2026-06-01 · ✅ погоджено

Як юзери з'являються на платформі, що можуть, як видаляються, як їхні дані потрапляють у BLUP-аналіз через схвалення. Цей документ — специфікація для імплементації, не поточний стан коду (частина рішень ще не закодована — позначено як 🚧 to do).


Дві осі ролей

На платформі дві окремі осі. Людина може бути власником своєї пасіки і одночасно помічником на чужій.

Ось A: platform-level (users.role)

Роль Хто це Що може
admin Розробник платформи Все. Реєструє senior'ів, бачить усі пасіки/спілки, перемикається в «👁 як пасічник»
senior Старший спілки (1 на спілку) Реєструє пасічників своєї спілки, підтверджує заявки, керує breeder_number у межах спілки
beekeeper Пасічник Працює зі своїми пасіками. Дефолт

Ось B: per-apiary (user_apiaries.role)

Контекст конкретної пасіки. beekeeper може мати різні ролі на різних пасіках одночасно.

Роль Що може на цій пасіці
owner 🏠 Повний контроль: CRUD структури, запрошення помічників. ОДИН на пасіку (CD-008)
manager 🛡 (Уповноважений) Те ж що owner, окрім видалення пасіки і передачі ownership
helper 🤝 (Помічник) Огляди, друк QR. Не CRUD структури

Beekeeper без власної пасіки — валідний кейс. Сезонний/найманий працівник, помічник у татка-пасічника. У БД: users.role=beekeeper, 0 рядків як owner у user_apiaries, ≥1 рядків як helper.

Статуси (users.status)

Статус Що означає
pending Подав заявку на самореєстрацію, чекає підтвердження senior/admin
active Підтверджений, повний доступ
rejected Відхилений senior'ом АБО soft-deleted (архів)

Спілки (associations)

Спілка = група пасічників на одного senior. Перший senior = адмін платформи (зараз — Віталій). При зростанні мережі — кожна нова група має свого senior'a.

Поле Що це
code 001, 002, … (3 цифри, generic — без прив'язки до інституції)
name Опціональна назва («Кафедра аграрного інституту», «Полтавська група», …)
senior_id users.id старшого спілки (не Telegram — це legacy назва колонки PRIMARY KEY)

Поточний стан (2026-06-01): одна спілка 001 (буде створена при імплементації), senior = Vitaliy Andriiuk.

breeder_number присвоюється послідовно у межах спілки: breeder №1 у спілці 001 — це Vitaliy. Наступний owner у спілці 001 → №2. У спілці 002 нумерація почнеться з 1 знову.


Як юзер потрапляє на платформу

Хто може самореєструватися (через сайт)

Роль Self-register? Через кого
admin ❌ ні Через env ADMIN_IDS (один)
senior ❌ ні Реєструє admin
owner ✅ так Сам через сайт АБО senior
manager ❌ ні Запрошує owner через magic-link
helper ❌ ні Запрошує owner через magic-link

Логіка: owner-flow має low friction (пасічник прийшов сам — пускаємо після перевірки senior'ом). Інші ролі — invitation-only для уникнення спаму і анонімних helper'ів.

Шлях 1: self-register owner'а

1. Юзер: beeu.com.ua/register
2. Форма: email, ПІБ, телефон, назва пасіки (опційно)
3. POST /api/register
   → users INSERT (role=beekeeper, status=pending)
   → apiaries INSERT (owner_id=новий uid, name=...)
   → senior спілки 001 отримує сповіщення (email)
4. Senior відкриває /dashboard → «📋 Заявки» → ✓ або ✕
   → ✓: status=active + breeder_number присвоєно
   → ✕: status=rejected (юзер бачить «Заявку відхилено»)
5. Юзер отримує email: «Прийнято / Відхилено»
6. Логінится через email + password (або magic-link)

Шлях 2: senior реєструє owner'а вручну

1. Senior → /dashboard → «👤 Користувачі» → «➕ Додати owner'a»
2. Форма: email, ПІБ, телефон, назва пасіки
3. POST /dashboard/add-user
   → users INSERT (role=beekeeper, status=active відразу)
   → apiaries INSERT (owner_id=новий uid)
   → breeder_number присвоєно
4. Юзер отримує email з посиланням для першого входу
   (magic-link, embedded — щоб задав пароль)

Шлях 3: owner запрошує manager/helper

1. Owner → /dashboard → Пасіки → «➕ Запросити помічника»
2. Форма: роль (manager/helper) + ОПЦІЇ доставки:
   a) Email: ввести email запрошуваного → платформа шле лист
   b) Copy link: згенерувати лінк, скопіювати, відправити
      будь-яким мессенджером (WhatsApp/Viber/Signal/Telegram особисто)
3. Сервер генерує invitation токен:
   → invitations INSERT (token, apiary_id, role, expires_at, invited_by)
   → URL: beeu.com.ua/invite/{token}
4. Запрошуваний відкриває посилання → форма:
   - Якщо вже зареєстрований (email знайдено) → залогінитись
     → автоматично додається у пасіку з вказаною роллю
   - Якщо ні → реєструється (email, ПІБ, телефон) → status=active
     одразу (бо вже запрошений owner'ом, approval не треба)
     → додається у пасіку
5. Токен інвалідовано

Чому email + copy link, без Telegram: Telegram-залежність прибрана. Email безкоштовний (Gmail SMTP / Sendgrid free tier), а copy-link дає owner'у гнучкість переслати через будь-який зручний канал.

Шлях 4: admin реєструє senior'а

1. Admin → /dashboard → «🧑‍🏫 Спілки» → «➕ Додати senior'a»
2. Форма: email, ПІБ, code спілки (002, 003, ...)
3. Backend:
   → users INSERT (role=senior, status=active)
   → associations INSERT (code, senior_id)
4. Email senior'у з посиланням для першого входу

Логін

Два способи (юзер обирає):

A) Email + password (стандарт)

  1. /login → email + пароль → авторизовано
  2. «Забув пароль?» → magic-link на email (one-time, 10 хв)
  3. Пароль зберігається у БД як bcrypt-хеш

B) Magic-link / passwordless (опція)

  1. /login → тільки email → «🔗 Надіслати посилання»
  2. Лист з посиланням beeu.com.ua/auth/{token} (одноразовий, 10 хв)
  3. Клік → залогінено
  4. Пароль не потрібен взагалі

Безпека magic-link: Знання чужого email НЕ дає доступ. Посилання приходить тільки у поштову скриньку власника email. Якщо пошта захищена (Google/iCloud password + 2FA) — magic-link рівно настільки ж безпечний як email+password.

Атака Email+password Magic-link
Знає email ⚠️ може брутфорсити пароль ✅ безпечно
Фішинг пароля ❌ зайде ✅ нема пароля
Витік бази ⚠️ хеш можна зламати ✅ нема паролів
Злам пошти ❌ reset → зайде ❌ link → зайде

Висновок: обидва способи мають одну спільну поверхню атаки — безпека пошти. Magic-link просто не вдає що пароль додає захист.

Захист посилання: - Час життя — 10 хв - Одноразове - HTTPS-only - Опційно 2FA для admin/senior (через SMS чи Authenticator app)


Що може юзер після реєстрації

Beekeeper-owner

Beekeeper-manager

Beekeeper-helper

Senior

Admin


Схвалення даних (BLUP-quality gate)

Мотивація: дисертація / BLUP/EBV пайплайн вимагає чистих даних. Помилки помічника (приклад: «сила 50 рамок» при норма 8-15) не повинні впливати на племінні цінності. Schвалення — gate-keeper якості.

Два рівні валідації

Рівень 1: платформа (автомат при save) - Числові межі per field (CD-перевіримо в protokoly_otsinjuvannia.md) - Sanity checks: colony_strength ≤ 30, varroa_percent ≤ 100, тощо - Soft warnings («сила 30+? перевір») — не блокує save - Hard rejects (від'ємні значення, текст у числовому полі) — блокує

Рівень 2: схвалення людиною (workflow per role)

                              ┌──────────────┐
PWA save → status='draft' ────▶ visit_records │
                              └──────┬───────┘
                                     │
                ┌────────────────────┴─────────────────────┐
                ▼                                          ▼
        Автор = owner пасіки?                   Автор = helper / manager?
        автоматично approved                    status='pending'
                                                ↓
                                       Owner відкриває «📋 Перевірити»
                                                ↓
                                          ✓ approve  ✕ reject
                                                ↓
                                  status='approved' або 'rejected'
                                                ↓
                                          Senior спілки
                                          бачить агрегат
                                          (опц. другий approve)

Хто кого схвалює

Запис від Хто схвалює Як
Owner своєї пасіки Автоматично approved при save Owner = власник даних, довіра 100%
Manager пасіки Owner пасіки «📋 Записи помічників → ✓/✕»
Helper пасіки Owner пасіки те ж саме
Senior для своїх даних Автоматично approved (він власник своїх)
Senior як 2-й рівень Опц. перевіряє approved записи спілки «📊 Записи спілки → 🔍 audit»

Колонки в БД (вже існують з 0006)

visit_records.data_status TEXT CHECK IN ('draft','pending','approved','rejected') visit_records.approved_by BIGINT REFERENCES users visit_records.approved_at TIMESTAMPTZ

Те ж саме для queens.

Поточний стан: усі записи draft (нічого не використовується). v1.6 запускає workflow.

BLUP/EBV pipeline filter

-- BLUP бере тільки approved дані:
SELECT * FROM visit_records WHERE data_status = 'approved'

Це жорсткий gate — жоден draft/pending/rejected запис не йде в племінні цінності. Гарантує якість.

UI компоненти 🚧

На дашборді owner'а: - 📋 Бейдж «N записів від помічників чекають» - Сторінка «Перевірити записи помічників»: - Список pending з фільтром по даті/помічнику - Кожен запис: ✓ Approve · ✕ Reject (опц. коментар чому) - Bulk approve (5-10 за раз)

На дашборді senior'а: - 📊 Огляд спілки: approved/pending/rejected per пасіка - 🔍 Audit-перегляд: відкрити будь-який approved запис, перевірити - ⚠️ Re-reject: senior може повернути approved→rejected (рідко)

На дашборді helper'а: - 📌 Бейдж «N ваших записів чекає схвалення» - 📌 «N відхилено» (можна побачити коментар owner'а) - Прозорість: бачить статус своїх записів

Що НЕ робимо

Edge cases


Видалення (soft-delete)

Beekeeper з пасіки (per-apiary)

Owner → Пасіки → ✕ біля helper/manager
→ DELETE FROM user_apiaries WHERE user_id=? AND apiary_id=?
→ Юзер втрачає доступ до пасіки
→ Огляди ЗАЛИШАЮТЬСЯ (recorded_by лишається той же uid)

Beekeeper повністю (платформа)

Senior спілки 00X → Користувачі → «🗄 Архівувати»
→ UPDATE users SET status='rejected' WHERE id=?
→ Юзер не може логінитись (login form каже «Обліковий запис архівовано»)
→ Усі огляди + структури ЗАЛИШАЮТЬСЯ (data integrity)
→ Можна повернути: UPDATE status='active'

Hard-delete (DELETE FROM users) — тільки SQL admin'ом у крайніх випадках (помилкова реєстрація, GDPR-запит). Каскади ON DELETE SET NULL зберігають історичні дані (recorded_by стає NULL).

Senior спілки

Якщо senior йде → admin призначає нового перед видаленням старого. Спілка не може лишитись без senior'а.


Що треба закодити (🚧 to do)

Phase 1: Foundation (✅ зроблено 2026-06-01)

# Завдання Статус
1 Міграція 0085: users.email/password_hash/email_verified_at
2 Міграція 0085: invitations таблиця
3 Seed: асоціація 001 + Vitaliy як senior
4 app/auth.py (bcrypt + invitation CRUD)
5 app/emails.py (Resend client + 4 шаблони)

Phase 3: Auth endpoints (🚧 наступне)

# Завдання Підстава
6 Endpoint /api/register (self-register owner) Шлях 1
7 Endpoint /api/login (email+password) Логін B
8 Endpoint /api/auth/{token} (magic-link login) Логін C
9 Endpoint /api/forgot-password Reset через magic-link
10 Endpoint /api/verify-email Шлях 1

Phase 4: Invitations + UI

# Завдання Підстава
11 Endpoint /dashboard/invite-helper (генерація invitation) Шлях 3
12 Endpoint /invite/{token} (прийом запрошення) Шлях 3
13 Сторінка /dashboard/approval-queue для senior'а Self-register schвалення
14 Кнопка «🗄 Архівувати» у списку юзерів Soft-delete

Phase 5: Data approval (NEW в v1.6)

# Завдання Підстава
15 Auto-set data_status='approved' для owner self-write Довіра owner'у
16 Auto-set data_status='pending' для helper/manager write Workflow
17 Сторінка «📋 Перевірити записи помічників» (owner) Approval queue
18 Endpoint /dashboard/approve-visit/{id} (✓/✕ з коментарем) Approval action
19 Бейдж «N pending» на головній owner'а UX
20 Сторінка «📊 Записи спілки» (senior, read-only audit) Senior 2-й рівень
21 Бейдж «N своїх pending/rejected» для helper Прозорість
22 Server-side валідація: межі numeric, sanity checks Гейт-1
23 BLUP-pipeline filter WHERE data_status='approved' Якість даних

Phase 6: Phase-out + polish

# Завдання Підстава
24 Прибрати з UI Telegram-логін, токени з бота Phase out
25 Опціонально: 2FA для admin/senior Безпека
26 Cron: cleanup expired invitations Гігієна БД

Принципи що тримаємо

  1. Telegram OUT — реєстрація і логін через email/сайт
  2. Спілка 001 — generic код, без hard-code інституцій
  3. Owner self-register, решта invitation-only — баланс friction/безпеки
  4. Магiclink-passwordless ≈ email+password з точки зору безпеки — обидва тримаються на захисті пошти
  5. Soft-delete як default — дані не губляться, тільки доступ
  6. Один beekeeper = одна пасіка (як owner) — CD-008 збережено
  7. Historical data integrity — FK ON DELETE SET NULL, recorded_by не зникає при видаленні юзера
  8. Data approval gate перед BLUP — owner схвалює дані помічників, senior робить 2-й рівень audit, тільки approved йде в племінні цінності
  9. Валідація на два рівні — platform автомат (межі, sanity) + людина (контекст: «чи не дурня?»)

Майбутнє (v2+)