From 50b01cf3f94c9baac24b0085d5c2d4d9c96521c7 Mon Sep 17 00:00:00 2001 From: dm Date: Mon, 24 Nov 2025 09:38:46 +0100 Subject: [PATCH] =?UTF-8?q?Poprawki=20w=20api=20i=20docker=20do=20wp=C3=B3?= =?UTF-8?q?=C5=82pracy=20sqlite?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 ++ docker-compose.prod.yml | 4 ++ src/pages/api/all-cities.ts | 13 ++-- src/pages/api/cities-autocomplete.ts | 45 ++++--------- src/pages/api/db.ts | 13 ++++ src/pages/api/has-streets.ts | 34 ++++------ src/pages/api/search.ts | 97 ++++++--------------------- src/pages/api/streets-autocomplete.ts | 47 +++++-------- 8 files changed, 91 insertions(+), 168 deletions(-) create mode 100644 src/pages/api/db.ts diff --git a/Dockerfile b/Dockerfile index 48acbdf..bf88adb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,16 +18,22 @@ FROM node:20-alpine WORKDIR /app +RUN mkdir -p /app/data + COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ +COPY ./src/data/ServicesRange.db ./data/ServicesRange.db + # YAML + public (runtime needed!) COPY --from=builder /app/src/content ./src/content COPY --from=builder /app/public ./public + ENV HOST=0.0.0.0 ENV PORT=4321 +ENV FUZ_DB_PATH=/app/data/ServicesRange.db EXPOSE 4321 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index a16d618..0c6973b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -9,10 +9,14 @@ services: restart: unless-stopped env_file: - .env + environment: + - FUZ_DB_PATH=/app/data/ServicesRange.db ports: - "4000:4321" networks: - n8n_default + volumes: + - ./data:/app/data networks: n8n_default: diff --git a/src/pages/api/all-cities.ts b/src/pages/api/all-cities.ts index 2ca1652..8a8223c 100644 --- a/src/pages/api/all-cities.ts +++ b/src/pages/api/all-cities.ts @@ -1,18 +1,13 @@ import type { APIRoute } from "astro"; -import Database from "better-sqlite3"; +import { getDb } from "./db"; type CityRow = { city: string }; export const GET: APIRoute = async () => { - const db = new Database("./src/data/ServicesRange.db", { readonly: true }); + const db = getDb(); + const rows = db.prepare("SELECT DISTINCT city FROM ranges ORDER BY city").all() as CityRow[]; - const rows = db - .prepare(`SELECT DISTINCT city FROM ranges ORDER BY city`) - .all() as CityRow[]; - - const cities = rows.map(r => r.city); - - return new Response(JSON.stringify(cities), { + return new Response(JSON.stringify(rows.map((x) => x.city)), { headers: { "Content-Type": "application/json" }, }); }; diff --git a/src/pages/api/cities-autocomplete.ts b/src/pages/api/cities-autocomplete.ts index a193252..12cac35 100644 --- a/src/pages/api/cities-autocomplete.ts +++ b/src/pages/api/cities-autocomplete.ts @@ -1,47 +1,30 @@ import type { APIRoute } from "astro"; -import Database from "better-sqlite3"; +import { getDb } from "./db"; -// 🔥 Funkcja normalizująca — identyczna jak w C# -function normalize(input: string): string { - return input - .normalize("NFD") - .replace(/\p{Diacritic}/gu, "") // usuwa ogonki - .toLowerCase(); +function normalize(s: string) { + return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase(); } -type CityRow = { - city: string; -}; +type CityRow = { city: string }; export const GET: APIRoute = async ({ request }) => { - const url = new URL(request.url); - const q = url.searchParams.get("q")?.trim() || ""; + const q = new URL(request.url).searchParams.get("q")?.trim() || ""; - if (q.length < 2) { - return new Response(JSON.stringify([]), { - headers: { "Content-Type": "application/json" }, - }); - } + if (q.length < 2) + return new Response("[]", { headers: { "Content-Type": "application/json" } }); - const db = new Database("./src/data/ServicesRange.db", { readonly: true }); + const db = getDb(); - // 🔥 Pobieramy wszystkie miasta (jest ich mało – to działa błyskawicznie) - const stmt = db.prepare(` - SELECT DISTINCT city - FROM ranges - `); + const rows = db.prepare(`SELECT DISTINCT city FROM ranges`).all() as CityRow[]; - const rows = stmt.all() as CityRow[]; + const nq = normalize(q); - // 🔥 Normalizujemy po stronie Node — ZERO problemów z kolacją SQLite - const pattern = normalize(q); - - const filtered = rows - .filter((row) => normalize(row.city).includes(pattern)) - .slice(0, 20) // LIMIT 20 + const result = rows + .filter((r) => normalize(r.city).includes(nq)) + .slice(0, 20) .map((r) => r.city); - return new Response(JSON.stringify(filtered), { + return new Response(JSON.stringify(result), { headers: { "Content-Type": "application/json" }, }); }; diff --git a/src/pages/api/db.ts b/src/pages/api/db.ts new file mode 100644 index 0000000..6a42aff --- /dev/null +++ b/src/pages/api/db.ts @@ -0,0 +1,13 @@ +import Database from "better-sqlite3"; + +const DB_PATH = + process.env.FUZ_DB_PATH || "./src/data/ServicesRange.db"; + +let db: Database.Database; + +export function getDb() { + if (!db) { + db = new Database(DB_PATH, { readonly: true }); + } + return db; +} diff --git a/src/pages/api/has-streets.ts b/src/pages/api/has-streets.ts index a2a829d..87e420a 100644 --- a/src/pages/api/has-streets.ts +++ b/src/pages/api/has-streets.ts @@ -1,35 +1,27 @@ import type { APIRoute } from "astro"; -import Database from "better-sqlite3"; - -interface HasStreetsRow { - cnt: number; -} +import { getDb } from "./db"; export const GET: APIRoute = async ({ request }) => { - const url = new URL(request.url); - const city = url.searchParams.get("city")?.trim() || ""; + const city = new URL(request.url).searchParams.get("city")?.trim() || ""; - if (!city) { + if (!city) return new Response(JSON.stringify({ hasStreets: false }), { headers: { "Content-Type": "application/json" }, }); - } - const db = new Database("./src/data/ServicesRange.db", { readonly: true }); + const db = getDb(); - const stmt = db.prepare(` - SELECT COUNT(*) AS cnt - FROM ranges - WHERE LOWER(city) = LOWER(?) - AND TRIM(street) <> '' - `); - - const row = stmt.get(city) as HasStreetsRow; + const row = db + .prepare( + `SELECT COUNT(*) AS cnt + FROM ranges + WHERE LOWER(city) = LOWER(?) + AND TRIM(street) <> ''` + ) + .get(city) as { cnt: number }; return new Response( - JSON.stringify({ - hasStreets: row.cnt > 0, - }), + JSON.stringify({ hasStreets: row.cnt > 0 }), { headers: { "Content-Type": "application/json" } } ); }; diff --git a/src/pages/api/search.ts b/src/pages/api/search.ts index 8a02691..7738156 100644 --- a/src/pages/api/search.ts +++ b/src/pages/api/search.ts @@ -1,85 +1,28 @@ import type { APIRoute } from "astro"; -import Database from "better-sqlite3"; - -interface RangeRow { - city: string; - street: string; - number: string; - availableFiber: number; - availableRadio: number; - lat: number; - lon: number; -} +import { getDb } from "./db"; export const POST: APIRoute = async ({ request }) => { - try { - const body = await request.json(); - const city = body.city?.trim() || ""; - const street = body.street?.trim() || ""; - const number = body.number?.trim() || ""; + const { city, street, number } = await request.json(); - if (!city || !number) { - return new Response(JSON.stringify({ ok: false }), { - headers: { "Content-Type": "application/json" }, - }); - } + const db = getDb(); - const db = new Database("./src/data/ServicesRange.db", { readonly: true }); - - const stmtHas = db.prepare(` - SELECT COUNT(*) AS cnt - FROM ranges - WHERE LOWER(city) = LOWER(?) AND TRIM(street) <> '' - `); - - const has = (stmtHas.get(city) as { cnt: number }).cnt > 0; - let row: RangeRow | undefined; - - if (!has) { - const stmt = db.prepare(` - SELECT * - FROM ranges - WHERE LOWER(city) = LOWER(?) - AND TRIM(number) = ? - LIMIT 1 - `); - row = stmt.get(city, number) as RangeRow | undefined; - - } else if (street === "") { - const stmt = db.prepare(` - SELECT * - FROM ranges - WHERE LOWER(city) = LOWER(?) - AND TRIM(number) = ? - LIMIT 1 - `); - row = stmt.get(city, number) as RangeRow | undefined; - - } else { - const stmt = db.prepare(` - SELECT * - FROM ranges - WHERE LOWER(city) = LOWER(?) + const row = db + .prepare( + `SELECT * + FROM ranges + WHERE LOWER(city) = LOWER(?) AND LOWER(street) = LOWER(?) - AND TRIM(number) = ? - LIMIT 1 - `); - row = stmt.get(city, street, number) as RangeRow | undefined; - } + AND LOWER(number) = LOWER(?) + LIMIT 1` + ) + .get(city, street || "", number || ""); - if (!row) { - return new Response(JSON.stringify({ ok: false }), { - headers: { "Content-Type": "application/json" }, - }); - } + if (!row) + return new Response(JSON.stringify({ ok: false }), { + headers: { "Content-Type": "application/json" }, + }); - return new Response( - JSON.stringify({ ok: true, result: row }), - { headers: { "Content-Type": "application/json" } } - ); - - } catch (err) { - console.error("SEARCH API ERROR:", err); - return new Response(JSON.stringify({ ok: false }), { status: 500 }); - } -}; + return new Response(JSON.stringify({ ok: true, result: row }), { + headers: { "Content-Type": "application/json" }, + }); +}; \ No newline at end of file diff --git a/src/pages/api/streets-autocomplete.ts b/src/pages/api/streets-autocomplete.ts index 1e3877b..ad711b8 100644 --- a/src/pages/api/streets-autocomplete.ts +++ b/src/pages/api/streets-autocomplete.ts @@ -1,49 +1,36 @@ import type { APIRoute } from "astro"; -import Database from "better-sqlite3"; +import { getDb } from "./db"; -type StreetRow = { - street: string; -}; - -// 🔥 Funkcja normalizująca — taka sama jak dla miast -function normalize(input: string): string { - return input - .normalize("NFD") - .replace(/\p{Diacritic}/gu, "") // usuwa ogonki - .toLowerCase(); +function normalize(s: string) { + return s.normalize("NFD").replace(/\p{Diacritic}/gu, "").toLowerCase(); } +type StreetRow = { street: string }; + export const GET: APIRoute = async ({ request }) => { const url = new URL(request.url); const city = url.searchParams.get("city")?.trim() || ""; const q = url.searchParams.get("q")?.trim() || ""; - if (!city || q.length < 1) { - return new Response(JSON.stringify([]), { - headers: { "Content-Type": "application/json" }, - }); - } + if (!city || q.length < 1) + return new Response("[]", { headers: { "Content-Type": "application/json" } }); - const db = new Database("./src/data/ServicesRange.db", { readonly: true }); + const db = getDb(); - // 🔥 Pobieramy WSZYSTKIE ulice tego miasta (jest ich mało) - const stmt = db.prepare(` - SELECT DISTINCT street - FROM ranges - WHERE city = ? - ORDER BY street - `); + const rows = db.prepare( + `SELECT DISTINCT street + FROM ranges + WHERE LOWER(city) = LOWER(?) + AND TRIM(street) <> ''` + ).all(city) as StreetRow[]; - const rows = stmt.all(city) as StreetRow[]; - - // 🔥 Normalizujemy porównanie — SQLite LIKE NIE JEST UŻYWANE const pattern = normalize(q); const filtered = rows - .filter((row) => normalize(row.street).includes(pattern)) - .slice(0, 20) - .map((r) => r.street); + .map((s) => s.street) + .filter((s) => normalize(s).includes(pattern)) + .slice(0, 20); return new Response(JSON.stringify(filtered), { headers: { "Content-Type": "application/json" },