1/**
2 * This application creates "The Here Times", a map-based news aggregator.
3 * It uses the Google Maps JavaScript API for map rendering, Geonames for location data,
4 * and the NewsAPI for fetching news articles.
5 * The app displays news for the top 12 most populous cities/neighborhoods in the current map view.
6 *
7 * We'll use the following approach:
8 * 1. Use Google Maps JavaScript API for rendering the map
9 * 2. Use Geonames API to get top 12 most populous cities within the map bounds
10 * 3. Fetch news data from NewsAPI based on these locations
11 * 4. Display news articles in a side drawer, only showing articles that contain the city's name in the title
12 * 5. Place markers on the map for each location (exactly 12)
15 */
16
17import { Loader } from "https://esm.sh/@googlemaps/js-api-loader@1.16.2";
18import ReactDOM from "https://esm.sh/react-dom@18.2.0";
19import React from "https://esm.sh/react@18.2.0";
37 const initMap = async () => {
38 try {
39 const response = await fetch("/api/maps-key");
40 const { apiKey } = await response.json();
41
42 const loader = new Loader({
43 apiKey: apiKey,
44 version: "weekly",
45 libraries: ["places"],
115 const fetchLocationsAndNews = async (south, west, north, east) => {
116 try {
117 const locationResponse = await fetch(`/api/locations?south=${south}&west=${west}&north=${north}&east=${east}`);
118 if (!locationResponse.ok) {
119 throw new Error(`HTTP error! status: ${locationResponse.status}`);
126
127 const newsPromises = limitedLocations.map(location =>
128 fetch(`/api/news?location=${encodeURIComponent(location.name)}`)
129 .then(async response => {
130 if (!response.ok) {
131 if (response.status === 429) {
132 console.warn("Rate limit reached for News API. Using placeholder data.");
133 return [{ title: "Rate limit reached", description: "Please try again later.", url: "#" }];
134 }
270 const url = new URL(request.url);
271
272 if (url.pathname === "/api/maps-key") {
273 const apiKey = Deno.env.get("GOOGLE_MAPS_API_KEY");
274 return new Response(JSON.stringify({ apiKey }), {
275 headers: { "Content-Type": "application/json" },
276 });
277 }
278
279 if (url.pathname === "/api/locations") {
280 const south = url.searchParams.get("south");
281 const west = url.searchParams.get("west");
292
293 const geonamesUrl =
294 `http://api.geonames.org/citiesJSON?south=${south}&west=${west}&north=${north}&east=${east}&maxRows=12&username=${geonamesUsername}&orderby=population`;
295
296 try {
297 const response = await fetch(geonamesUrl);
298 if (!response.ok) {
299 throw new Error(`Geonames API responded with status: ${response.status}`);
300 }
301 const data = await response.json();
317 }
318
319 if (url.pathname === "/api/news") {
320 try {
321 const location = url.searchParams.get("location");
322 const newsApiKey = Deno.env.get("NEWS_API_KEY");
323
324 if (!newsApiKey) {
325 console.error("NEWS_API_KEY is not set");
326 throw new Error("NEWS_API_KEY is not set");
327 }
328
329 const apiUrl = `https://newsapi.org/v2/everything?q=${
330 encodeURIComponent(location || '')
331 }&sortBy=publishedAt&apiKey=${newsApiKey}`;
332
333 if (!location) {
335 }
336
337 console.log(`Fetching news for ${location} from ${apiUrl}`);
338
339 const response = await fetch(apiUrl);
340
341 if (!response.ok) {
342 if (response.status === 429) {
343 console.warn("Rate limit reached for News API. Returning placeholder data.");
344 return new Response(
345 JSON.stringify([
352 }
353 const errorText = await response.text();
354 console.error(`News API error: ${response.status} ${response.statusText}. Body: ${errorText}`);
355 throw new Error(`News API responded with status: ${response.status}. Error: ${errorText}`);
356 }
357
359
360 if (!data.articles || !Array.isArray(data.articles)) {
361 console.error("Unexpected response format from News API:", data);
362 throw new Error("Unexpected response format from News API");
363 }
364
377 });
378 } catch (error) {
379 console.error("Server-side error in /api/news:", error);
380 return new Response(JSON.stringify({ error: error.message }), {
381 status: 500,