Database — низкоуровневый CRUD¶
Database-секция админ-панели — это прямой доступ к произвольным таблицам Supabase через bridge. По сути SQL-консоль с UI, но ограниченная whitelisted-таблицами для безопасности.
Что внутри¶
flowchart LR
UI[UI таблица] --> List[adminCatalogList
SELECT * FROM <table>]
UI --> Update[adminCatalogUpdate
UPDATE <table> SET ... WHERE id]
UI --> Delete[adminCatalogDelete
UPDATE <table> SET is_deleted=true]
UI --> Find[adminFindByHash
SELECT WHERE sha256=$1]
UI --> Wipe[adminWipeAll
DELETE FROM <table> — only on staging]
Whitelist таблиц (заданный в AppBridge):
private static readonly HashSet<string> _allowedAdminTables = new(StringComparer.Ordinal)
{
"redux_items",
"redux_versions",
"gunpacks",
"gunpack_guns",
"armor_packs",
"armor_pieces",
"dlc_imports",
"library_components",
"gta_versions",
"user_builds",
"popular_choices",
"pro_players",
"admin_notes",
};
private void EnsureAllowedTable(string table)
{
if (!_allowedAdminTables.Contains(table))
throw new InvalidOperationException($"Table '{table}' not in admin whitelist");
}
Это первая линия защиты — UI может попросить «обнови row в users таблице», но bridge откажет. users и auth.* админка трогать не может (это уровень Supabase auth, отдельный flow).
Handlers¶
["adminCatalogList"] = async payload =>
{
var table = payload?.GetProperty("table").GetString()!;
var search = payload?.TryGetProperty("search", out var s) == true ? s.GetString() : null;
return await _bridge.AdminCatalogListAsync(table, search);
},
["adminCatalogUpdate"] = async payload =>
{
var table = payload?.GetProperty("table").GetString()!;
var id = payload?.GetProperty("id").GetString()!;
var patch = payload?.GetProperty("patch") ?? throw new ArgumentException("patch required");
return await _bridge.AdminCatalogUpdateAsync(table, id, patch);
},
["adminCatalogDelete"] = async payload =>
{
var table = payload?.GetProperty("table").GetString()!;
var id = payload?.GetProperty("id").GetString()!;
return await _bridge.AdminCatalogDeleteAsync(table, id);
},
AdminCatalogUpdateAsync принимает arbitrary JSON patch и шлёт в Supabase как update().eq('id', ...). Это даёт админу полную свободу менять любую field — пять минут можно обновить description одного мода или массово переименовать author'а через скрипт.
Soft-delete всегда¶
AdminCatalogDeleteAsync никогда не делает DELETE FROM. Всегда UPDATE ... SET is_deleted = true. Причины:
- Восстановление. Админ случайно удалил redux — можно вернуть руками через
is_deleted = false. - Юзеры с ссылками. Если row удалена hard, у юзера в install-state.json остаётся
reduxIdкоторый дереферится в null — UI крашится. С soft-delete мы можем показать «Мод удалён, но вот что у вас стоит» вместо ошибки. - R2-файлы. Real DELETE требовал бы также чистить R2 — отдельная сложная задача, см. r2-layout.
Hard-delete делается отдельной утилитой r2_gc.ps1 раз в месяц вручную.
adminFindByHash¶
Полезная фича для дебага:
public async Task<List<FindByHashRow>> AdminFindByHashAsync(string sha)
{
// Ищем по нескольким таблицам и колонкам
var rows = new List<FindByHashRow>();
rows.AddRange(await _supa.QueryAsync("redux_versions", $"sha256 = '{sha}'"));
rows.AddRange(await _supa.QueryAsync("gunpacks", $"rpf_sha256 = '{sha}'"));
rows.AddRange(await _supa.QueryAsync("dlc_imports", $"sha256 = '{sha}'"));
return rows;
}
Use case: юзер прислал лог [inject] dirty file abc123def456... — админ кидает этот SHA в Find, видит «это из мода Y версии Z», понимает что юзер ставил другой мод до нашего.
adminWipeAll¶
public async Task<int> AdminWipeAllAsync(string table)
{
if (!_environment.IsStaging)
throw new InvalidOperationException("adminWipeAll allowed only on staging env");
EnsureAllowedTable(table);
return await _supa.DeleteAllAsync(table);
}
На production environment гард IsStaging блокирует. Stage- и dev-инстансы Supabase можно clean'уть одной кнопкой при тестах.
SQL editor¶
В UI это табличный инспектор + edit-modal — не raw SQL. Это намеренно: raw SQL даёт админу слишком много власти (можно случайно сломать БД). Если нужны сложные запросы — открываем Supabase Studio напрямую через браузер (см. infra/supabase-schema).