# `modules/users` — профиль и Cyberhack ## Профиль - `GET /users/me` — возвращает `ProfileData`, собранный одним агрегирующим SQL-запросом (`repository::get_aggregated_profile`): юзер + текущая подписка (если есть, кроме `expired`) + список рефералов одним JSON-объектом. Это осознанно сделано на стороне БД (`jsonb_build_object`/`jsonb_agg`), а не в Rust несколькими запросами. ## Cyberhack (мини-игра) — server-authoritative Вся игровая логика (генерация доски, проверка ходов, подсчёт очков) продублирована на сервере в [`cyberhack.rs`](cyberhack.rs) — порт 1:1 алгоритма из `Cyberhack/src/lib.rs` (WASM/Yew-движок игры). Раньше клиент сам генерировал доску, сам считал `score` и просто присылал готовое число (сервер лишь клампил его в `0..=500`) — это давало клиенту полный контроль над наградой. Флоу теперь в два шага: 1. **`POST /users/hack/start`** — списывает билет (та же атомарная транзакция с `SELECT ... FOR UPDATE` + минимум 45 секунд между попытками, что и раньше, см. `repository::start_hack_session`), генерирует доску/цели на сервере, сохраняет их в `hack_sessions` (TTL = время игры + запас на сетевую задержку) и отдаёт клиенту `{session_id, matrix, targets, base_value, time_limit}`. Клиент (WASM-игра) обязан играть строго на этой доске, не генерировать свою. 2. **`POST /users/hack/result`** (`{ session_id, path }`) — `path` это сырая последовательность кликов `(row, col)` в порядке совершения, а не итоговый счёт. `repository::consume_hack_session` атомарно помечает сессию использованной (повторная сдача — отказ) и возвращает сохранённую доску; `cyberhack::replay_path` реплеит путь, проверяя легальность каждого хода (чередование строка/столбец, без повторных клеток, лимит буфера), и сам считает, какие цели реально выполнены. Награда начисляется по этому результату — то, что мог бы прислать клиент, нигде не используется. Билеты выдаются только при покупке подписки (`billing::buy_subscription`, +1 билет, кэп 10) — то есть поиграть в Cyberhack без действующей/когда-либо купленной подписки нельзя.