mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-01 21:02:12 +03:00
* feat(dashboard): add Provider Quota visibility toggle per connection * refactor(dashboard): extract provider quota visibility controls Move quota visibility UI and update logic into reusable components, add Portuguese translations, and remove the stale migration gap allowlist entry. * Hide quota visibility controls for unsupported providers * chore(ci): retrigger GitHub checks * fix(db): renumber quota-visibility migration past release tip (121→125) 122_free_proxy_sync_errors.sql, 123_quota_auto_ping.sql, and 124_generic_session_affinity_ttl.sql have since landed on release/v3.8.49, so 121 is now out-of-sequence and would not apply on databases already past 122+. Renumbers to 125 (the next free slot past the current release tip) and restores "121" in check-migration-numbering's KNOWN_GAPS allowlist, since 121 remains a genuine unfilled gap once this migration moves off that number. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * chore(quality): rebaseline file-size + complexity for resync merge The release-resync merge unions two already-compliant features in the same god-component (ConnectionRow.tsx/ConnectionsListPanel.tsx): this PR's per-connection quota-visibility wiring and release's confirm- delete-account wiring (#7361). Both were individually within budget (785/786 lines); combined they land at 791. Complexity count moves 2058->2059 for the same reason (2 previously-compliant .map() render callbacks in ConnectionsListPanel.tsx now marginally exceed the 80-line function cap). No new logic was written — see the _rebaseline_2026_07_18_pr7360_quota_visibility_resync justification entries in both baseline files for the full accounting. Verified via a byte-for-byte diff of the violation lists between origin/release/ v3.8.49 tip and this merge. Structural shrink stays tracked in #3501. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> * refactor(db): split _updateConnectionRow update assembly (complexity gate) _updateConnectionRow grew past the 80-line max-lines-per-function ceiling after this branch added quota_visible column handling. Extract the `.run()` params assembly (field mapping/normalization, unchanged) into a module-private `_buildUpdateConnectionRowParams` helper in the same file so the SQL statement + call site stay in `_updateConnectionRow` while the function itself drops back under the gate. No behavior change. Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
/**
|
|
* db/caseMapping.ts — pure snake_case ↔ camelCase column mapping.
|
|
*
|
|
* Extracted from db/core.ts (god-file decomposition): the column-name conversion
|
|
* helpers that translate raw SQLite rows (snake_case columns, 0/1 booleans, `_json`
|
|
* TEXT columns) into the camelCase shapes the domain modules consume. Pure — no DB
|
|
* handle, no module state — so they live as a co-located leaf that every db/ module
|
|
* (and core.ts itself) imports. core.ts re-exports all five so existing call sites that
|
|
* pull these helpers off the core module keep working unchanged.
|
|
*/
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
const BOOLEAN_CAMEL_COLUMNS = new Set([
|
|
"isActive",
|
|
"rateLimitProtection",
|
|
"proxyEnabled",
|
|
"perKeyProxyEnabled",
|
|
"quotaVisible",
|
|
]);
|
|
|
|
export function toSnakeCase(str: string): string {
|
|
return str.replace(/([A-Z])/g, "_$1").toLowerCase();
|
|
}
|
|
|
|
export function toCamelCase(str: string): string {
|
|
return str.replace(/_([a-z])/g, (_: string, c: string) => c.toUpperCase());
|
|
}
|
|
|
|
export function objToSnake(obj: unknown): unknown {
|
|
if (!obj || typeof obj !== "object") return obj;
|
|
const result: JsonRecord = {};
|
|
for (const [k, v] of Object.entries(obj as JsonRecord)) {
|
|
result[toSnakeCase(k)] = v;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function rowToCamel(row: unknown): JsonRecord | null {
|
|
if (!row) return null;
|
|
const result: JsonRecord = {};
|
|
for (const [k, v] of Object.entries(row as JsonRecord)) {
|
|
const camelKey = toCamelCase(k);
|
|
if (BOOLEAN_CAMEL_COLUMNS.has(camelKey)) {
|
|
result[camelKey] = v === 1 || v === true;
|
|
} else if (camelKey === "providerSpecificData" && typeof v === "string") {
|
|
try {
|
|
result[camelKey] = JSON.parse(v);
|
|
} catch {
|
|
result[camelKey] = v;
|
|
}
|
|
} else if (camelKey.endsWith("Json")) {
|
|
// Convention: any column with a `_json` suffix is JSON-encoded TEXT.
|
|
// Surface the parsed object under the friendlier name (key minus the
|
|
// "Json" suffix) — e.g. quotaWindowThresholdsJson → quotaWindowThresholds.
|
|
// A NULL/absent column normalizes to `baseKey: null` (not the suffixed
|
|
// key) so read and write paths expose a consistent shape.
|
|
const baseKey = camelKey.slice(0, -"Json".length);
|
|
if (typeof v === "string") {
|
|
try {
|
|
result[baseKey] = JSON.parse(v);
|
|
} catch {
|
|
result[baseKey] = null;
|
|
}
|
|
} else {
|
|
result[baseKey] = v == null ? null : v;
|
|
}
|
|
} else {
|
|
result[camelKey] = v;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
export function cleanNulls(obj: unknown): JsonRecord {
|
|
const result: JsonRecord = {};
|
|
for (const [k, v] of Object.entries((obj as JsonRecord) || {})) {
|
|
if (v !== null && v !== undefined) {
|
|
result[k] = v;
|
|
}
|
|
}
|
|
return result;
|
|
}
|