fix(ui): полный справочник стран и городов как в VPS Tracker
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
quality / api (push) Successful in 36s
CD / quality (push) Successful in 1m30s
CD / publish (push) Successful in 1m51s
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 4s
quality / docker-check (push) Skipped
quality / web (push) Successful in 45s
quality / api (push) Successful in 36s
CD / quality (push) Successful in 1m30s
CD / publish (push) Successful in 1m51s
Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Vendored
+7
-1
@@ -2863,6 +2863,12 @@ type AliasRow = {
|
||||
};
|
||||
declare function listLocations(db: Db): LocationRow[];
|
||||
declare function getLocation(db: Db, locationId: string): LocationRow;
|
||||
/**
|
||||
* Дополняет таблицу locations городами из geo-каталога (тот же, что VPS Tracker).
|
||||
* Существующие коды (msk/fra/…) не перезаписываются.
|
||||
*/
|
||||
declare function ensureCatalogLocations(db: Db): number;
|
||||
declare function findLocationByCity(db: Db, cityName: string, countryCode?: string | null): LocationRow | null;
|
||||
declare function listZones(db: Db): ZoneRow[];
|
||||
declare function getZone(db: Db, zoneId: string): ZoneRow;
|
||||
declare function createZone(db: Db, input: {
|
||||
@@ -2987,4 +2993,4 @@ declare function dashboardCounts(db: Db): {
|
||||
lastSyncAt: string | null;
|
||||
};
|
||||
|
||||
export { type AliasRow, type AppSettingsDto, type AppSettingsPatch, ConflictError, type Db, type LocationRow, type NodeAddressRow, type NodeRow, NotFoundError, type Sqlite, type ZoneRow, addSyncEvent, aliases, appSettings, createAlias, createDb, createMemoryDb, createNode, createSyncJob, createZone, dashboardCounts, deleteAlias, deleteNode, deleteZone, getAlias, getAppSettings, getLocation, getNode, getSyncJob, getZone, healthCheck, ignoreOrphan, ignoredOrphans, listAliases, listIgnoredOrphans, listLocations, listNodes, listSyncJobs, listZones, locations, nodeAddresses, nodes, resolveDatabasePath, runMigrations, schema, syncEvents, syncJobs, unignoreOrphan, updateAlias, updateAppSettings, updateNode, updateSyncJob, updateZone, zones };
|
||||
export { type AliasRow, type AppSettingsDto, type AppSettingsPatch, ConflictError, type Db, type LocationRow, type NodeAddressRow, type NodeRow, NotFoundError, type Sqlite, type ZoneRow, addSyncEvent, aliases, appSettings, createAlias, createDb, createMemoryDb, createNode, createSyncJob, createZone, dashboardCounts, deleteAlias, deleteNode, deleteZone, ensureCatalogLocations, findLocationByCity, getAlias, getAppSettings, getLocation, getNode, getSyncJob, getZone, healthCheck, ignoreOrphan, ignoredOrphans, listAliases, listIgnoredOrphans, listLocations, listNodes, listSyncJobs, listZones, locations, nodeAddresses, nodes, resolveDatabasePath, runMigrations, schema, syncEvents, syncJobs, unignoreOrphan, updateAlias, updateAppSettings, updateNode, updateSyncJob, updateZone, zones };
|
||||
|
||||
Vendored
+62
@@ -236,6 +236,7 @@ function updateAppSettings(db, patch) {
|
||||
// src/fleet-repo.ts
|
||||
import { randomUUID } from "crypto";
|
||||
import { and, asc, count, eq as eq2, sql as sql2 } from "drizzle-orm";
|
||||
import { catalogCitiesWithLocCodes } from "@cdnmanager/shared";
|
||||
function now() {
|
||||
return (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
|
||||
}
|
||||
@@ -272,6 +273,65 @@ function getLocation(db, locationId) {
|
||||
if (!row) throw new NotFoundError(`location ${locationId}`);
|
||||
return mapLocation(row);
|
||||
}
|
||||
function ensureCatalogLocations(db) {
|
||||
const existing = db.select().from(locations).all();
|
||||
const byCode = new Map(existing.map((r) => [r.code.toLowerCase(), r]));
|
||||
const byNameCountry = new Map(
|
||||
existing.map((r) => [
|
||||
`${(r.name || "").trim().toLowerCase()}|${(r.country || "").toUpperCase()}`,
|
||||
r
|
||||
])
|
||||
);
|
||||
let inserted = 0;
|
||||
let sortOrder = 100;
|
||||
for (const city of catalogCitiesWithLocCodes()) {
|
||||
const nameKey = `${city.name.trim().toLowerCase()}|${city.countryCode}`;
|
||||
if (byNameCountry.has(nameKey)) continue;
|
||||
if (byCode.has(city.locCode.toLowerCase())) {
|
||||
const alt = `${city.locCode}${city.countryCode.toLowerCase()}`;
|
||||
if (byCode.has(alt) || byNameCountry.has(nameKey)) continue;
|
||||
db.insert(locations).values({
|
||||
id: `loc-${alt}`,
|
||||
code: alt,
|
||||
name: city.name,
|
||||
country: city.countryCode,
|
||||
sort_order: sortOrder++
|
||||
}).run();
|
||||
byCode.set(alt, { id: `loc-${alt}` });
|
||||
byNameCountry.set(nameKey, { id: `loc-${alt}` });
|
||||
inserted += 1;
|
||||
continue;
|
||||
}
|
||||
db.insert(locations).values({
|
||||
id: `loc-${city.locCode}`,
|
||||
code: city.locCode,
|
||||
name: city.name,
|
||||
country: city.countryCode,
|
||||
sort_order: sortOrder++
|
||||
}).run();
|
||||
byCode.set(city.locCode.toLowerCase(), {
|
||||
id: `loc-${city.locCode}`
|
||||
});
|
||||
byNameCountry.set(nameKey, {
|
||||
id: `loc-${city.locCode}`
|
||||
});
|
||||
inserted += 1;
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
function findLocationByCity(db, cityName, countryCode) {
|
||||
const q = cityName.trim().toLowerCase();
|
||||
if (!q) return null;
|
||||
const rows = listLocations(db);
|
||||
const match = rows.find((l) => {
|
||||
if (l.name.trim().toLowerCase() !== q) return false;
|
||||
if (countryCode && l.country && l.country.toUpperCase() !== countryCode.toUpperCase()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return match ?? null;
|
||||
}
|
||||
function listZones(db) {
|
||||
return db.select().from(zones).orderBy(asc(zones.name)).all().map(mapZone);
|
||||
}
|
||||
@@ -645,6 +705,8 @@ export {
|
||||
deleteAlias,
|
||||
deleteNode,
|
||||
deleteZone,
|
||||
ensureCatalogLocations,
|
||||
findLocationByCity,
|
||||
getAlias,
|
||||
getAppSettings,
|
||||
getLocation,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { and, asc, count, eq, sql } from "drizzle-orm";
|
||||
import { catalogCitiesWithLocCodes } from "@cdnmanager/shared";
|
||||
import type { Db } from "./client.js";
|
||||
import { NotFoundError, ConflictError } from "./errors.js";
|
||||
import {
|
||||
@@ -126,6 +127,82 @@ export function getLocation(db: Db, locationId: string): LocationRow {
|
||||
return mapLocation(row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Дополняет таблицу locations городами из geo-каталога (тот же, что VPS Tracker).
|
||||
* Существующие коды (msk/fra/…) не перезаписываются.
|
||||
*/
|
||||
export function ensureCatalogLocations(db: Db): number {
|
||||
const existing = db.select().from(locations).all();
|
||||
const byCode = new Map(existing.map((r) => [r.code.toLowerCase(), r]));
|
||||
const byNameCountry = new Map(
|
||||
existing.map((r) => [
|
||||
`${(r.name || "").trim().toLowerCase()}|${(r.country || "").toUpperCase()}`,
|
||||
r,
|
||||
]),
|
||||
);
|
||||
|
||||
let inserted = 0;
|
||||
let sortOrder = 100;
|
||||
for (const city of catalogCitiesWithLocCodes()) {
|
||||
const nameKey = `${city.name.trim().toLowerCase()}|${city.countryCode}`;
|
||||
if (byNameCountry.has(nameKey)) continue;
|
||||
if (byCode.has(city.locCode.toLowerCase())) {
|
||||
// код занят другой записью — пробуем расширенный код
|
||||
const alt = `${city.locCode}${city.countryCode.toLowerCase()}`;
|
||||
if (byCode.has(alt) || byNameCountry.has(nameKey)) continue;
|
||||
db.insert(locations)
|
||||
.values({
|
||||
id: `loc-${alt}`,
|
||||
code: alt,
|
||||
name: city.name,
|
||||
country: city.countryCode,
|
||||
sort_order: sortOrder++,
|
||||
})
|
||||
.run();
|
||||
byCode.set(alt, { id: `loc-${alt}` } as (typeof existing)[0]);
|
||||
byNameCountry.set(nameKey, { id: `loc-${alt}` } as (typeof existing)[0]);
|
||||
inserted += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
db.insert(locations)
|
||||
.values({
|
||||
id: `loc-${city.locCode}`,
|
||||
code: city.locCode,
|
||||
name: city.name,
|
||||
country: city.countryCode,
|
||||
sort_order: sortOrder++,
|
||||
})
|
||||
.run();
|
||||
byCode.set(city.locCode.toLowerCase(), {
|
||||
id: `loc-${city.locCode}`,
|
||||
} as (typeof existing)[0]);
|
||||
byNameCountry.set(nameKey, {
|
||||
id: `loc-${city.locCode}`,
|
||||
} as (typeof existing)[0]);
|
||||
inserted += 1;
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
|
||||
export function findLocationByCity(
|
||||
db: Db,
|
||||
cityName: string,
|
||||
countryCode?: string | null,
|
||||
): LocationRow | null {
|
||||
const q = cityName.trim().toLowerCase();
|
||||
if (!q) return null;
|
||||
const rows = listLocations(db);
|
||||
const match = rows.find((l) => {
|
||||
if (l.name.trim().toLowerCase() !== q) return false;
|
||||
if (countryCode && l.country && l.country.toUpperCase() !== countryCode.toUpperCase()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return match ?? null;
|
||||
}
|
||||
|
||||
export function listZones(db: Db): ZoneRow[] {
|
||||
return db
|
||||
.select()
|
||||
|
||||
Reference in New Issue
Block a user