added apiKey

This commit is contained in:
Radek Davidek 2025-11-01 00:17:15 +01:00
parent 04f3ef22e8
commit 6d0a22a314
2 changed files with 100 additions and 59 deletions

View File

@ -12,6 +12,10 @@ import java.nio.charset.StandardCharsets;
import java.util.List;
public class HttpServerApp {
// Statický API klíč
private static final String API_KEY = "JustSomeRandomText";
public static void main(String[] args) throws Exception {
TransmissionService service = new TransmissionService();
service.loadNextDays(10);
@ -23,6 +27,8 @@ public class HttpServerApp {
server.createContext("/transmissions", exchange -> {
try {
setCors(exchange);
if (!checkApiKey(exchange)) return;
List<Transmission> all = service.getAll();
respondJson(exchange, mapper.writeValueAsString(all));
} catch (Exception e) {
@ -35,6 +41,8 @@ public class HttpServerApp {
server.createContext("/search", exchange -> {
try {
setCors(exchange);
if (!checkApiKey(exchange)) return;
URI uri = exchange.getRequestURI();
String query = null;
if (uri.getQuery() != null) {
@ -53,10 +61,12 @@ public class HttpServerApp {
}
});
// NEW: /refresh endpoint
// /refresh endpoint
server.createContext("/refresh", exchange -> {
try {
setCors(exchange);
if (!checkApiKey(exchange)) return;
if ("GET".equalsIgnoreCase(exchange.getRequestMethod())) {
service.reloadDataAsync();
respondJson(exchange, "{\"status\":\"ok\",\"message\":\"Data se obnovují na pozadí.\"}");
@ -96,17 +106,44 @@ public class HttpServerApp {
server.start();
System.out.println("🚀 HTTP server běží na http://localhost:8080");
System.out.println(" ➜ /transmissions (všechny přenosy JSON)");
System.out.println(" ➜ /search?q=Brno (vyhledávání JSON)");
System.out.println(" ➜ /refresh (spustí opětovné načtení dat)");
System.out.println(" ➜ /transmissions (všechny přenosy JSON, vyžaduje X-API-KEY)");
System.out.println(" ➜ /search?q=Brno (vyhledávání JSON, vyžaduje X-API-KEY)");
System.out.println(" ➜ /refresh (spustí opětovné načtení dat, vyžaduje X-API-KEY)");
System.out.println(" ➜ / (web UI)");
}
// ======= API Key ochrana =======
private static boolean checkApiKey(HttpExchange exchange) throws IOException {
String query = exchange.getRequestURI().getQuery(); // např. apiKey=xxxx
String key = null;
if (query != null) {
for (String part : query.split("&")) {
if (part.startsWith("apiKey=")) {
key = java.net.URLDecoder.decode(part.substring(7), StandardCharsets.UTF_8);
break;
}
}
}
if (!API_KEY.equals(key)) {
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
byte[] bytes = "{\"error\":\"Unauthorized\"}".getBytes(StandardCharsets.UTF_8);
exchange.sendResponseHeaders(401, bytes.length);
try (OutputStream os = exchange.getResponseBody()) {
os.write(bytes);
}
return false;
}
return true;
}
// ======= CORS =======
private static void setCors(HttpExchange exchange) {
Headers h = exchange.getResponseHeaders();
h.set("Access-Control-Allow-Origin", "*");
h.set("Access-Control-Allow-Methods", "GET, OPTIONS");
h.set("Access-Control-Allow-Headers", "Content-Type");
h.set("Access-Control-Allow-Headers", "Content-Type, X-API-KEY");
if ("OPTIONS".equalsIgnoreCase(exchange.getRequestMethod())) {
try {
exchange.sendResponseHeaders(204, -1);
@ -116,6 +153,7 @@ public class HttpServerApp {
}
}
// ======= JSON odpověď =======
private static void respondJson(HttpExchange exchange, String json) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "application/json; charset=utf-8");
byte[] bytes = json.getBytes(StandardCharsets.UTF_8);
@ -125,6 +163,7 @@ public class HttpServerApp {
}
}
// ======= Chyba =======
private static void sendError(HttpExchange exchange, int code, String message) throws IOException {
exchange.getResponseHeaders().set("Content-Type", "text/plain; charset=utf-8");
byte[] bytes = message.getBytes(StandardCharsets.UTF_8);
@ -134,6 +173,7 @@ public class HttpServerApp {
}
}
// ======= Načtení resource =======
private static String readResource(String resourcePath) {
try (InputStream is = HttpServerApp.class.getResourceAsStream(resourcePath)) {
if (is == null) return null;

View File

@ -66,6 +66,9 @@
<script>
(function () {
const urlParams = new URLSearchParams(window.location.search);
const apiKey = urlParams.get('apiKey') || '';
const API = '/transmissions';
let all = [];
let filtered = [];
@ -82,18 +85,20 @@
const refreshBtn = document.getElementById('refreshBtn');
function fetchAll() {
fetch(API).then(r => r.json()).then(data => {
all = data || [];
populateFilters();
applyFilters();
}).catch(err => {
console.error(err);
alert('Chyba při načítání dat: ' + err);
});
fetch(API + '?apiKey=' + encodeURIComponent(apiKey))
.then(r => r.json())
.then(data => {
all = data || [];
populateFilters();
applyFilters();
})
.catch(err => {
console.error(err);
alert('Chyba při načítání dat: ' + err);
});
}
function populateFilters() {
// Sport filter
const sports = Array.from(new Set(all.map(x => x.sport).filter(Boolean))).sort();
sportFilter.innerHTML = '<option value="">— Všechny sporty —</option>';
sports.forEach(s => {
@ -102,7 +107,6 @@
populateLeagueFilter();
// Date filter
const dates = Array.from(new Set(all.map(x => x.date).filter(Boolean))).sort();
dateFilter.innerHTML = '<option value="">— Všechna data —</option>';
dates.forEach(d => {
@ -121,33 +125,30 @@
}
function normalizeText(s) {
if (!s) return '';
// odstranění diakritiky a převod na malá písmena
return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
}
if (!s) return '';
return s.normalize('NFD').replace(/\p{Diacritic}/gu, '').toLowerCase();
}
function applyFilters() {
const q = normalizeText(searchBox.value.trim());
const sport = sportFilter.value;
const league = leagueFilter.value;
const date = dateFilter.value;
function applyFilters() {
const q = normalizeText(searchBox.value.trim());
const sport = sportFilter.value;
const league = leagueFilter.value;
const date = dateFilter.value;
filtered = all.filter(t => {
if (sport && t.sport !== sport) return false;
if (league && t.league !== league) return false;
if (date && t.date !== date) return false;
if (!q) return true;
filtered = all.filter(t => {
if (sport && t.sport !== sport) return false;
if (league && t.league !== league) return false;
if (date && t.date !== date) return false;
if (!q) return true;
const text = normalizeText((t.title||'') + ' ' + (t.sport||'') + ' ' + (t.league||''));
return text.indexOf(q) !== -1;
});
// spojíme text a normalizujeme
const text = normalizeText((t.title||'') + ' ' + (t.sport||'') + ' ' + (t.league||''));
return text.indexOf(q) !== -1;
});
sortFiltered();
currentPage = 1;
renderTable();
renderPager();
}
sortFiltered();
currentPage = 1;
renderTable();
renderPager();
}
function sortFiltered() {
const ord = sortOrder.value;
@ -221,33 +222,33 @@
}
let debounceTimer;
searchBox.addEventListener('input', () => {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(applyFilters, 250);
});
searchBox.addEventListener('input', () => { clearTimeout(debounceTimer); debounceTimer = setTimeout(applyFilters, 250); });
sportFilter.addEventListener('change', () => { populateLeagueFilter(); applyFilters(); });
leagueFilter.addEventListener('change', applyFilters);
dateFilter.addEventListener('change', applyFilters);
sortOrder.addEventListener('change', () => { sortFiltered(); renderTable(); });
refreshBtn.addEventListener('click', async () => {
try {
refreshBtn.disabled = true;
refreshBtn.textContent = "Načítám...";
const resp = await fetch('/refresh');
await resp.json();
setTimeout(() => {
fetchAll();
function refreshData() {
refreshBtn.disabled = true;
refreshBtn.textContent = "Načítám...";
fetch('/refresh?apiKey=' + encodeURIComponent(apiKey))
.then(r => r.json())
.then(() => {
setTimeout(() => {
fetchAll();
refreshBtn.disabled = false;
refreshBtn.textContent = "Refresh";
}, 7000);
})
.catch(err => {
console.error(err);
alert('Chyba při obnově dat: ' + err);
refreshBtn.disabled = false;
refreshBtn.textContent = "Refresh";
}, 7000);
} catch (err) {
console.error(err);
alert('Chyba při obnově dat: ' + err);
refreshBtn.disabled = false;
refreshBtn.textContent = "Refresh";
}
});
});
}
refreshBtn.addEventListener('click', refreshData);
fetchAll();