Restrict saatavuus API to return only true/false

Requires exact match of osoite + postinumero + kaupunki.
No longer exposes addresses, speeds, or any customer data.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 02:43:58 +02:00
parent b927cd2bf5
commit 37ffd7e46b
3 changed files with 36 additions and 60 deletions

64
api.php
View File

@@ -287,70 +287,40 @@ switch ($action) {
break; break;
} }
$query = normalizeAddress($_GET['osoite'] ?? ''); // Parametrit: osoite (kadunnimi + numero), postinumero, kaupunki
$postinumero = trim($_GET['postinumero'] ?? ''); $queryOsoite = normalizeAddress($_GET['osoite'] ?? '');
$queryPostinumero = trim($_GET['postinumero'] ?? '');
$queryKaupunki = strtolower(trim($_GET['kaupunki'] ?? ''));
if (empty($query) && empty($postinumero)) { if (empty($queryOsoite) || empty($queryPostinumero) || empty($queryKaupunki)) {
http_response_code(400); http_response_code(400);
echo json_encode(['error' => 'Anna osoite tai postinumero']); echo json_encode(['error' => 'Anna osoite, postinumero ja kaupunki']);
break; break;
} }
$customers = loadCustomers(); $customers = loadCustomers();
$matches = []; $found = false;
foreach ($customers as $c) { foreach ($customers as $c) {
foreach ($c['liittymat'] ?? [] as $l) { foreach ($c['liittymat'] ?? [] as $l) {
$addr = normalizeAddress($l['asennusosoite'] ?? ''); $addr = normalizeAddress($l['asennusosoite'] ?? '');
$zip = trim($l['postinumero'] ?? ''); $zip = trim($l['postinumero'] ?? '');
$city = strtolower(trim($l['kaupunki'] ?? '')); $city = strtolower(trim($l['kaupunki'] ?? ''));
$hit = false;
// Postinumero-haku // Kaikki kolme pitää mätsätä: osoite, postinumero, kaupunki
if (!empty($postinumero) && $zip === $postinumero) { if ($zip === $queryPostinumero && $city === $queryKaupunki) {
$hit = true; // Osoite-match: tarkka sisältö-match
} if (!empty($addr) && !empty($queryOsoite)) {
if (strpos($addr, $queryOsoite) !== false || strpos($queryOsoite, $addr) !== false) {
// Osoitehaku (sisältää haun) $found = true;
if (!empty($query) && !empty($addr)) { break 2;
if (strpos($addr, $query) !== false || strpos($query, $addr) !== false) { }
$hit = true;
} }
// Kadunnimi-match (ilman numeroa)
$queryStreet = trim(preg_replace('/\d+.*$/', '', $query));
$addrStreet = trim(preg_replace('/\d+.*$/', '', $addr));
if (!empty($queryStreet) && !empty($addrStreet) && strpos($addrStreet, $queryStreet) !== false) {
$hit = true;
}
}
if ($hit) {
// Palauta VAIN osoitetieto ja nopeus - ei asiakastietoja
$matches[] = [
'osoite' => $l['asennusosoite'] ?? '',
'postinumero' => $zip,
'kaupunki' => $l['kaupunki'] ?? '',
'nopeus' => $l['liittymanopeus'] ?? '',
];
} }
} }
} }
// Poista duplikaatit (sama osoite eri asiakkailla) // Palauta VAIN true/false - ei osoitteita, nopeuksia tai muuta dataa
$unique = []; echo json_encode(['saatavilla' => $found]);
$seen = [];
foreach ($matches as $m) {
$key = normalizeAddress($m['osoite'] . $m['postinumero']);
if (!isset($seen[$key])) {
$unique[] = $m;
$seen[$key] = true;
}
}
echo json_encode([
'saatavilla' => count($unique) > 0,
'kohteet' => $unique,
'maara' => count($unique),
]);
break; break;
// ---------- CONFIG (admin) ---------- // ---------- CONFIG (admin) ----------

View File

@@ -286,17 +286,22 @@
<div style="margin-bottom:0.75rem;"><strong>Endpoint:</strong><br>GET https://intra.cuitunet.fi/api.php?action=saatavuus</div> <div style="margin-bottom:0.75rem;"><strong>Endpoint:</strong><br>GET https://intra.cuitunet.fi/api.php?action=saatavuus</div>
<div style="margin-bottom:0.75rem;"><strong>Parametrit:</strong><br> <div style="margin-bottom:0.75rem;"><strong>Parametrit:</strong><br>
&bull; <code>key</code> = API-avain (pakollinen)<br> &bull; <code>key</code> = API-avain (pakollinen)<br>
&bull; <code>osoite</code> = Haettava osoite (esim. "Kauppakatu 5")<br> &bull; <code>osoite</code> = Katuosoite ja numero (esim. "Kauppakatu 5")<br>
&bull; <code>postinumero</code> = Postinumero (esim. "20100")<br> &bull; <code>postinumero</code> = Postinumero (esim. "20100")<br>
Anna vähintään toinen: osoite tai postinumero.</div> &bull; <code>kaupunki</code> = Kaupunki (esim. "Turku")<br>
Kaikki kolme pakollisia.</div>
<div style="margin-bottom:0.75rem;"><strong>Esimerkki:</strong><br> <div style="margin-bottom:0.75rem;"><strong>Esimerkki:</strong><br>
<code id="api-example-url">api.php?action=saatavuus&key=AVAIN&osoite=Kauppakatu+5</code></div> <code id="api-example-url">api.php?action=saatavuus&key=AVAIN&osoite=Kauppakatu+5&postinumero=20100&kaupunki=Turku</code></div>
<div><strong>Vastaus:</strong><br> <div><strong>Vastaus:</strong><br>
<code>{"saatavilla":true,"kohteet":[{"osoite":"...","postinumero":"...","kaupunki":"...","nopeus":"..."}],"maara":1}</code></div> <code>{"saatavilla":true}</code> tai <code>{"saatavilla":false}</code></div>
</div> </div>
<h3 style="color:#0f3460;margin:1.5rem 0 1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Testaa API</h3> <h3 style="color:#0f3460;margin:1.5rem 0 1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Testaa API</h3>
<div style="display:flex;gap:0.5rem;max-width:500px;"> <div style="display:grid;grid-template-columns:1fr;gap:0.5rem;max-width:500px;">
<input type="text" id="test-api-address" placeholder="Osoite tai postinumero" style="flex:1;"> <input type="text" id="test-api-address" placeholder="Osoite (esim. Kauppakatu 5)">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:0.5rem;">
<input type="text" id="test-api-zip" placeholder="Postinumero" maxlength="5">
<input type="text" id="test-api-city" placeholder="Kaupunki">
</div>
<button class="btn-primary" id="btn-test-api">Testaa</button> <button class="btn-primary" id="btn-test-api">Testaa</button>
</div> </div>
<pre id="test-api-result" style="margin-top:0.75rem;background:#f8f9fb;padding:1rem;border-radius:8px;font-size:0.85rem;display:none;overflow-x:auto;"></pre> <pre id="test-api-result" style="margin-top:0.75rem;background:#f8f9fb;padding:1rem;border-radius:8px;font-size:0.85rem;display:none;overflow-x:auto;"></pre>

View File

@@ -928,7 +928,7 @@ async function loadSettings() {
document.getElementById('settings-api-key').value = config.api_key || ''; document.getElementById('settings-api-key').value = config.api_key || '';
document.getElementById('settings-cors').value = (config.cors_origins || ['https://cuitunet.fi', 'https://www.cuitunet.fi']).join('\n'); document.getElementById('settings-cors').value = (config.cors_origins || ['https://cuitunet.fi', 'https://www.cuitunet.fi']).join('\n');
const key = config.api_key || 'AVAIN'; const key = config.api_key || 'AVAIN';
document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${key}&osoite=Kauppakatu+5`; document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${key}&osoite=Kauppakatu+5&postinumero=20100&kaupunki=Turku`;
} catch (e) { console.error(e); } } catch (e) { console.error(e); }
} }
@@ -936,7 +936,7 @@ document.getElementById('btn-generate-key').addEventListener('click', async () =
try { try {
const config = await apiCall('generate_api_key', 'POST'); const config = await apiCall('generate_api_key', 'POST');
document.getElementById('settings-api-key').value = config.api_key || ''; document.getElementById('settings-api-key').value = config.api_key || '';
document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${config.api_key}&osoite=Kauppakatu+5`; document.getElementById('api-example-url').textContent = `api.php?action=saatavuus&key=${config.api_key}&osoite=Kauppakatu+5&postinumero=20100&kaupunki=Turku`;
} catch (e) { alert(e.message); } } catch (e) { alert(e.message); }
}); });
@@ -951,16 +951,17 @@ document.getElementById('btn-save-settings').addEventListener('click', async ()
}); });
document.getElementById('btn-test-api').addEventListener('click', async () => { document.getElementById('btn-test-api').addEventListener('click', async () => {
const address = document.getElementById('test-api-address').value.trim(); const osoite = document.getElementById('test-api-address').value.trim();
const postinumero = document.getElementById('test-api-zip').value.trim();
const kaupunki = document.getElementById('test-api-city').value.trim();
const apiKey = document.getElementById('settings-api-key').value; const apiKey = document.getElementById('settings-api-key').value;
if (!address) { alert('Anna osoite tai postinumero'); return; } if (!osoite || !postinumero || !kaupunki) { alert('Täytä osoite, postinumero ja kaupunki'); return; }
const result = document.getElementById('test-api-result'); const result = document.getElementById('test-api-result');
result.style.display = 'block'; result.style.display = 'block';
result.textContent = 'Haetaan...'; result.textContent = 'Haetaan...';
try { try {
const isZip = /^\d{5}$/.test(address); const params = `osoite=${encodeURIComponent(osoite)}&postinumero=${encodeURIComponent(postinumero)}&kaupunki=${encodeURIComponent(kaupunki)}`;
const param = isZip ? `postinumero=${encodeURIComponent(address)}` : `osoite=${encodeURIComponent(address)}`; const res = await fetch(`${API}?action=saatavuus&key=${encodeURIComponent(apiKey)}&${params}`);
const res = await fetch(`${API}?action=saatavuus&key=${encodeURIComponent(apiKey)}&${param}`);
const data = await res.json(); const data = await res.json();
result.textContent = JSON.stringify(data, null, 2); result.textContent = JSON.stringify(data, null, 2);
} catch (e) { result.textContent = 'Virhe: ' + e.message; } } catch (e) { result.textContent = 'Virhe: ' + e.message; }