PRO Players — топовые сборки¶
Секция админ-панели для публичных билдов от профессиональных игроков и стримеров. По сути это редакционно курируемая подборка «вот сборка топ-стримера X — поставь себе одной кнопкой».
Отличие от user_builds: pro_players — это админ-добавленные сборки знаковых игроков, не user-submitted. Админ сам собирает payload, заполняет про игрока, публикует.
Структура таблицы¶
create table pro_players (
id uuid primary key,
player_name text not null, -- "Niko Hunter", "BulkinGotti"
team text, -- "Vortex", "Flame"
avatar_url text,
cover_url text,
description text, -- bio
twitch_url text,
youtube_url text,
hnt_code text unique, -- ссылка на их билд
build_payload jsonb, -- snapshot их установки
display_order int default 0,
is_featured bool default false,
is_deleted bool default false
);
Воркфлоу¶
Админ договаривается с игроком, тот говорит «у меня redux X v3, gunpack Y, armor Z». Админ:
- У себя на ПК ставит тот же билд.
- Открывает админ-панель → PRO Players → Add player.
- Вводит имя/команду, прикладывает аватар.
- Жмёт «Захватить мой текущий setup» — лаунчер делает
CaptureHntPayloadAsync()(то же что для HNT-codes). - Получает
hnt_code— сохраняется в БД, привязан к pro_player row.
Capture-handler¶
public async Task<ProPlayerDto> AdminProPlayerCreateAsync(ProPlayerDraftDto draft)
{
// Захват текущего payload админа
var payload = await CaptureHntPayloadAsync();
// Генерим публичный hnt_code (как для user_builds approve)
var code = await _hntCodesRepo.GenerateUniqueCodeAsync();
await _hntCodesRepo.InsertPublicAsync(code,
JsonSerializer.SerializeToElement(payload, _hntJsonOpts));
// Upload assets
var avatarUrl = draft.AvatarBytes is null ? null
: await _r2.UploadAsync($"pro-players/{draft.Id}/avatar.png", draft.AvatarBytes);
var coverUrl = draft.CoverBytes is null ? null
: await _r2.UploadAsync($"pro-players/{draft.Id}/cover.png", draft.CoverBytes);
// Insert
return await _supa.InsertProPlayerAsync(new ProPlayerRow {
Id = draft.Id, PlayerName = draft.PlayerName, Team = draft.Team,
AvatarUrl = avatarUrl, CoverUrl = coverUrl,
TwitchUrl = draft.TwitchUrl, YoutubeUrl = draft.YoutubeUrl,
Description = draft.Description, HntCode = code,
BuildPayload = JsonSerializer.SerializeToElement(payload, _hntJsonOpts),
IsFeatured = draft.IsFeatured,
});
}
UI юзера¶
Юзер в Browse → PRO Players видит грид аватарок. Клик → карточка игрока с описанием, twitch-link'ом, видео-демо, и большой кнопкой «Поставить билд». Кнопка вызывает HntCodeApplyAsync с привязанным hnt_code.
Это самый «магический» юзерский сценарий — один клик и у тебя точно такой же setup как у любимого стримера. Идея заимствована из MMO «купить сетап в один клик».
Update билда¶
Когда игрок меняет setup (новая ENB, обновлённый gunpack), админ перезахватывает:
public async Task<ProPlayerDto> AdminProPlayerRefreshBuildAsync(string proPlayerId)
{
// Тот же flow как в Create, но обновляем existing row
var payload = await CaptureHntPayloadAsync();
var code = await _hntCodesRepo.GenerateUniqueCodeAsync();
await _hntCodesRepo.InsertPublicAsync(code,
JsonSerializer.SerializeToElement(payload, _hntJsonOpts));
// Старый hnt_code остаётся валидным — юзеры с ним получат старый билд.
// Это intentional чтобы те кто уже забукмаркал не "потеряли" билд.
return await _supa.UpdateProPlayerBuildAsync(proPlayerId, code, payload);
}
Featured¶
is_featured = true ставит игрока в топ секции на главной. Обычно держим 5-8 featured.
Privacy и creds¶
Аватарки и описания берём от самих игроков с разрешением. Twitch/YouTube links без affiliate-кодов (мы не зарабатываем на cross-promotion'е). Если игрок просит убрать — soft-delete.