diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..49b9f4a --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +# DB_DATABASE = "./world.db" +ORIGIN=http://localhost:3000 +PGHOST=localhost +PGPORT=5432 +PGUSERNAME=user +PGPASSWORD=password +PGDATABASE=pg_db diff --git a/database/data.db b/database/data.db deleted file mode 100644 index 5b24b4c..0000000 Binary files a/database/data.db and /dev/null differ diff --git a/src/lib/db/index.ts b/src/lib/db/index.ts index b204db4..ca35423 100644 --- a/src/lib/db/index.ts +++ b/src/lib/db/index.ts @@ -1,6 +1,20 @@ -//import Database from 'better-sqlite3'; -import { Database } from 'bun:sqlite'; +// import { Database } from 'bun:sqlite'; +// const db = new Database('./database/data.db'); -const db = new Database('./database/data.db'); +import { SQL } from 'bun'; + +const db = new SQL({ + // Pool configuration + max: 20, // Maximum 20 concurrent connections + idleTimeout: 30, // Close idle connections after 30s + maxLifetime: 3600, // Max connection lifetime 1 hour + connectionTimeout: 10, // Connection timeout 10s + onconnect: (client) => { + console.log('Connected to database'); + }, + onclose: (client) => { + console.log('Connection closed'); + } +}); export default db; diff --git a/src/lib/db/queries.ts b/src/lib/db/queries.ts index 514698f..a9a5318 100644 --- a/src/lib/db/queries.ts +++ b/src/lib/db/queries.ts @@ -1,127 +1,156 @@ -import db from "."; - -// export const allCountries = db.query('SELECT id, name, native, emoji FROM countries'); -export const allCountries = db.query(` - SELECT id, name, native, emoji - FROM countries +// import db from '.'; +import type { CityBasic, CityTemperature, CityTemperaturSimple, CountryBasic } from '$lib/types'; +import { sql } from 'bun'; +// // export const allCountries = await sql('SELECT id, name, native, emoji FROM countries'); +export const allCountries = (await sql` + SELECT id, name, native, emoji + FROM countries WHERE id IN( - SELECT DISTINCT country_id - FROM cities + SELECT DISTINCT country_id + FROM cities WHERE capital IN('capital', 'admin') OR(population >= 100000 AND population IS NOT NULL) ) -`); +`) as CountryBasic[]; -export const allCities = db.query("SELECT id, city as name, country_id, lat as latitude, lng as longitude FROM cities WHERE capital IN ('capital', 'admin') OR (population >= 100000 AND population != '')"); -export const allCitiesWithNoTemperatureToday = db.query(` +export const allCities = + (await sql`SELECT id, city AS name, country_id, lat AS latitude, lng AS longitude + FROM cities + WHERE capital IN ('capital', 'admin') + OR (population >= 100000 AND population::text != '')`) as CityBasic[]; + +export const allCitiesWithNoTemperatureToday = async ({ + date +}: { + date: string; +}): Promise => + await sql` SELECT c.id, c.city as name, c.country_id, c.lat as latitude, c.lng as longitude FROM cities c - LEFT JOIN meteo_data md ON c.id = md.city_id AND md.date = $date - WHERE (c.capital IN ('capital', 'admin') OR (c.population >= 100000 AND c.population != '')) + LEFT JOIN meteo_data md ON c.id = md.city_id AND md.date = ${date} + WHERE (c.capital IN ('capital', 'admin') OR (c.population >= 100000 AND c.population::text != '')) AND md.city_id IS NULL; -`); +`; -export const cityTemperatureToday = db.query(` +export const cityTemperatureToday = async ({ + id, + date +}: { + id: number; + date: string; +}): Promise => + await sql` SELECT c.id, c.city as name, md.min, md.max, md.date FROM cities as c JOIN meteo_data as md ON c.id = md.city_id - WHERE c.id = $id - AND md.date = $date -`); + WHERE c.id = ${id} + AND md.date = ${date} +`; -export const insertCityTemperatureData = db.query(` +export const insertCityTemperatureData = async ({ + city_id, + min, + max, + date +}: CityTemperaturSimple): Promise => + await sql` INSERT INTO meteo_data (city_id, min, max, date) - VALUES ($id, $min, $max, $date) -`); -export const getAllCitiesInCountryByTemp = db.query(` - SELECT - c.id as city_id, - c.city as city_name, - md.max as max_temp, - md.min as min_temp, - md.date - FROM cities c - JOIN meteo_data md ON c.id = md.city_id - WHERE c.country_id = $country_id - AND md.date = $date - ORDER BY md.max DESC -`); + VALUES (${city_id}, ${min}, ${max}, ${date}) + RETURNING * +`; -export const getHottestAndColdestCityInCountry = db.query(` +// export const getAllCitiesInCountryByTemp = await sql` +// SELECT +// c.id as city_id, +// c.city as city_name, +// md.max as max_temp, +// md.min as min_temp, +// md.date +// FROM cities c +// JOIN meteo_data md ON c.id = md.city_id +// WHERE c.country_id = $country_id +// AND md.date = $date +// ORDER BY md.max DESC +// `; + +export const getHottestAndColdestCityInCountry = async ({ + country_id, + date +}: { + country_id: number; + date: string; +}) => + await sql` WITH city_temps AS ( - SELECT - c.id as city_id, - c.city as city_name, - c.country_id, - md.max as max_temp, - md.date, - ROW_NUMBER() OVER (PARTITION BY c.country_id ORDER BY md.max DESC) as hottest_rank, - ROW_NUMBER() OVER (PARTITION BY c.country_id ORDER BY md.max ASC) as coldest_rank - FROM cities c - JOIN meteo_data md ON c.id = md.city_id - WHERE md.date = $date - AND c.country_id = $country_id - ) - SELECT * FROM city_temps - WHERE hottest_rank = 1 OR coldest_rank = 1 -`); - -export const getHottestAndColdestCityInWorld = db.query(` - WITH city_temps AS ( - SELECT - c.id as city_id, - c.city as city_name, - c.country_id, - co.name as country_name, - md.max as max_temp, - md.min as min_temp, - md.date, - ROW_NUMBER() OVER (ORDER BY md.max DESC) as hottest_rank, - ROW_NUMBER() OVER (ORDER BY md.min ASC) as coldest_rank - FROM cities c - JOIN meteo_data md ON c.id = md.city_id - JOIN countries co ON c.country_id = co.id - WHERE md.date = $date - ) - SELECT * FROM city_temps - WHERE hottest_rank = 1 OR coldest_rank = 1 - LIMIT 2 -`); - - -export const getWorldTemperatureStats = db.query(` - WITH city_temps AS ( - SELECT - c.id as city_id, - c.city as city_name, - c.country_id, - co.name as country_name, - md.max as max_temp, - md.min as min_temp, - md.date, - ROW_NUMBER() OVER (ORDER BY md.max DESC) as hottest_rank, - ROW_NUMBER() OVER (ORDER BY md.max ASC) as coldest_rank - FROM cities c - JOIN meteo_data md ON c.id = md.city_id - JOIN countries co ON c.country_id = co.id - WHERE md.date = $date - ), - world_stats AS ( SELECT - ct1.city_id, - ct1.city_name, - ct1.country_id, - ct1.country_name, - ct1.max_temp, - ct1.date, - ct1.hottest_rank, - ct1.coldest_rank - FROM city_temps ct1 - WHERE ct1.hottest_rank = 1 OR ct1.coldest_rank = 1 + c.id as city_id, + c.city as city_name, + c.country_id, + ROUND(md.max::numeric, 2) as max_temp, + md.date, + ROW_NUMBER() OVER (PARTITION BY c.country_id ORDER BY md.max DESC)::INTEGER as hottest_rank, + ROW_NUMBER() OVER (PARTITION BY c.country_id ORDER BY md.max ASC)::INTEGER as coldest_rank + FROM cities c + JOIN meteo_data md ON c.id = md.city_id + WHERE md.date = ${date} + AND c.country_id = ${country_id} ) - SELECT * FROM world_stats - ORDER BY max_temp DESC - LIMIT 2 -`); - + SELECT * FROM city_temps + WHERE hottest_rank = 1 OR coldest_rank = 1 +`; +// export const getHottestAndColdestCityInWorld = await sql` +// WITH city_temps AS ( +// SELECT +// c.id as city_id, +// c.city as city_name, +// c.country_id, +// co.name as country_name, +// md.max as max_temp, +// md.min as min_temp, +// md.date, +// ROW_NUMBER() OVER (ORDER BY md.max DESC) as hottest_rank, +// ROW_NUMBER() OVER (ORDER BY md.min ASC) as coldest_rank +// FROM cities c +// JOIN meteo_data md ON c.id = md.city_id +// JOIN countries co ON c.country_id = co.id +// WHERE md.date = $date +// ) +// SELECT * FROM city_temps +// WHERE hottest_rank = 1 OR coldest_rank = 1 +// LIMIT 2 +// `; +// export const getWorldTemperatureStats = await sql` +// WITH city_temps AS ( +// SELECT +// c.id as city_id, +// c.city as city_name, +// c.country_id, +// co.name as country_name, +// md.max as max_temp, +// md.min as min_temp, +// md.date, +// ROW_NUMBER() OVER (ORDER BY md.max DESC) as hottest_rank, +// ROW_NUMBER() OVER (ORDER BY md.max ASC) as coldest_rank +// FROM cities c +// JOIN meteo_data md ON c.id = md.city_id +// JOIN countries co ON c.country_id = co.id +// WHERE md.date = $date +// ), +// world_stats AS ( +// SELECT +// ct1.city_id, +// ct1.city_name, +// ct1.country_id, +// ct1.country_name, +// ct1.max_temp, +// ct1.date, +// ct1.hottest_rank, +// ct1.coldest_rank +// FROM city_temps ct1 +// WHERE ct1.hottest_rank = 1 OR ct1.coldest_rank = 1 +// ) +// SELECT * FROM world_stats +// ORDER BY max_temp DESC +// LIMIT 2 +// `; diff --git a/src/lib/server/meteoDataHandler.ts b/src/lib/server/meteoDataHandler.ts index 984e3ea..b52b0ec 100644 --- a/src/lib/server/meteoDataHandler.ts +++ b/src/lib/server/meteoDataHandler.ts @@ -1,56 +1,53 @@ -import { allCities, allCitiesWithNoTemperatureToday, cityTemperatureToday, insertCityTemperatureData } from "$lib/db/queries"; -import type { CityBasic, CityTemperature } from "$lib/types"; -import { getLocationTemperature } from "./meteoService"; +import { allCitiesWithNoTemperatureToday, insertCityTemperatureData } from '$lib/db/queries'; +import { getLocationTemperature } from './meteoService'; const COOLDOWN_TIME = 333; export const fetchAllCitiesTemperatures = async (date: string) => { - try { + try { + const cities = await allCitiesWithNoTemperatureToday({ date }); + console.log('Today date: ', date); + console.log('Total Cities to fetch: ', cities.length); - const cities = allCitiesWithNoTemperatureToday.all({ $date: date }) as CityBasic[]; + for (const [index, city] of cities.entries()) { + try { + console.log(`Processing city ${index + 1}/${cities.length}`); - console.log('Today date: ', date); - console.log('Total Cities to fetch: ', cities.length); + const { data } = await getLocationTemperature( + city.id, + city.latitude.toString(), + city.longitude.toString() + ); - for (const [index, city] of cities.entries()) { - try { - console.log(`Processing city ${index + 1}/${cities.length}`); - const existingRecord = cityTemperatureToday.get({ $id: city.id, $date: date }) as CityTemperature | null; - if (!existingRecord) { - - const { data } = await getLocationTemperature( - city.id, - city.latitude.toString(), - city.longitude.toString() - ); - if (data) { - insertCityTemperatureData.run({ - $id: data.id, - $min: data.min, - $max: data.max, - $date: data.date - }); - console.log(`Temperature data stored for ${city.name}`); - } - // Add cooldown delay to avoid API throttling - await new Promise(resolve => setTimeout(resolve, COOLDOWN_TIME)); - } else { - console.log(`Temperature data already exists for ${city.name}`); - } + if (data) { + await insertCityTemperatureData({ + city_id: city.id, // Changed from data.id to city.id + min: data.min, + max: data.max, + date: data.date + }); + console.log(`Temperature data stored for ${city.name}`); + } else { + console.log(`No temperature data available for ${city.name}`); + } + // Add cooldown delay to avoid API throttling + await new Promise((resolve) => setTimeout(resolve, COOLDOWN_TIME)); + } catch (error) { + console.error(`Failed to fetch temperature for city ${city.name}:`, error); + // Consider adding more specific error handling here + } + } - } catch (error) { - console.error(`Failed to fetch temperature for city ${city.name}:`, error); - } - } - console.log('Finished fetching temperatures for all cities: ', date); - return { - ok: true, - } - } catch (error) { - console.error('Failed to fetch cities:', error); - return { - ok: false - } - } -} \ No newline at end of file + console.log('Finished fetching temperatures for all cities: ', date); + return { + ok: true + }; + } catch (error) { + console.error('Failed to fetch cities:', error); + return { + ok: false, + error: error?.message // Added error message for better debugging + }; + } +}; diff --git a/src/lib/server/meteoService.ts b/src/lib/server/meteoService.ts index 8252535..111d43f 100644 --- a/src/lib/server/meteoService.ts +++ b/src/lib/server/meteoService.ts @@ -1,38 +1,37 @@ -import type { MeteoData, MeteoResponseType } from "$lib/types"; +import type { MeteoData, MeteoResponseType } from '$lib/types'; export const getLocationTemperature = async (id: number, latitude: string, longitude: string) => { - const meteoEndpoint = "https://api.open-meteo.com/v1/forecast"; - // https://api.open-meteo.com/v1/forecast?latitude=48.0356&longitude=27.8129&hourly=temperature_2m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1&format=json&timeformat=unixtime + const meteoEndpoint = 'https://api.open-meteo.com/v1/forecast'; + // https://api.open-meteo.com/v1/forecast?latitude=48.0356&longitude=27.8129&hourly=temperature_2m&daily=temperature_2m_max,temperature_2m_min&forecast_days=1&format=json&timeformat=unixtime - const url = new URL(meteoEndpoint); - url.searchParams.set('latitude', latitude); - url.searchParams.set('longitude', longitude); - url.searchParams.set('daily', 'temperature_2m_max,temperature_2m_min'); - url.searchParams.set('format', 'json'); - url.searchParams.set('forecast_days', '1'); + const url = new URL(meteoEndpoint); + url.searchParams.set('latitude', latitude); + url.searchParams.set('longitude', longitude); + url.searchParams.set('daily', 'temperature_2m_max,temperature_2m_min'); + url.searchParams.set('format', 'json'); + url.searchParams.set('forecast_days', '1'); - - try { - const response = await fetch(url); - if (response.ok) { - const data = await response.json() as MeteoResponseType; - console.log('Fetched DATA from open meteo >>>>'); - return { - data: { - id, - max: data.daily.temperature_2m_max[0], - min: data.daily.temperature_2m_min[0], - date: data.daily.time[0], - } as MeteoData - }; - } else { - return { data: null }; - } - - } catch (error) { - console.log(error); - return { - data: null - } - } -} \ No newline at end of file + try { + const response = await fetch(url); + console.log('Fetching data from open meteo...'); + if (response.ok) { + const data = (await response.json()) as MeteoResponseType; + console.log('Fetched DATA from open meteo >>>>'); + return { + data: { + id, + max: data.daily.temperature_2m_max[0], + min: data.daily.temperature_2m_min[0], + date: data.daily.time[0] + } as MeteoData + }; + } else { + return { data: null }; + } + } catch (error) { + console.log(error); + return { + data: null + }; + } +}; diff --git a/src/lib/types.ts b/src/lib/types.ts index 8940ef4..f18ffd4 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -1,72 +1,79 @@ export type CountryBasic = { - id: number; - name: string; - native: string; - emoji: string; + id: number; + name: string; + native: string; + emoji: string; }; export type CityBasic = { - id: number; - name: string; - country_id: number; - latitude: number; - longitude: number; + id: number; + name: string; + country_id: number; + latitude: number; + longitude: number; }; export type CityTemperature = { - id: number; - name: string; - min: number; - max: number; - date: string; -} + id: number; + name: string; + min: number; + max: number; + date: string; +}; + +export type CityTemperaturSimple = { + city_id: number; + min: number; + max: number; + date: string; +}; export type HotColdCity = { - city_id: number; - city_name: string; - country_id: number; - country_name: string; - max_temp: number; - date: string; - hottest_rank: number; - coldest_rank: number; -} + city_id: number; + city_name: string; + country_id: number; + country_name: string; + max_temp: number; + date: string; + hottest_rank: number; + coldest_rank: number; +}; export type ComboOption = { - id: number; - name: string; - emoji?: string; - country_id?: number; -} + id: number; + name: string; + emoji?: string; + country_id?: number; +}; export type MeteoResponseType = { - latitude: number; - longitude: number; - generationtime_ms: number; - utc_offset_seconds: number; - timezone: string; - timezone_abbreviation: string; - elevation: number; - daily_units: { - time: string; - temperature_2m_max: string; - temperature_2m_min: string; - }, - daily: { - time: string[]; - temperature_2m_max: number[]; - temperature_2m_min: number[]; - }, -} + latitude: number; + longitude: number; + generationtime_ms: number; + utc_offset_seconds: number; + timezone: string; + timezone_abbreviation: string; + elevation: number; + daily_units: { + time: string; + temperature_2m_max: string; + temperature_2m_min: string; + }; + daily: { + time: string[]; + temperature_2m_max: number[]; + temperature_2m_min: number[]; + }; +}; export type MeteoData = { - id: number; - max: number; - min: number; - date: string; -} + id: number; + max: number; + min: number; + date: string; +}; export enum UnitsEnum { - METRIC = 'METRIC', - IMPERIAL = 'IMPERIAL' + METRIC = 'METRIC', + IMPERIAL = 'IMPERIAL' } diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index f54ac89..907ea9b 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,4 +1,9 @@ -import { allCities, allCountries, getHottestAndColdestCityInCountry } from '$lib/db/queries'; +import { + allCities, + allCitiesWithNoTemperatureToday, + allCountries, + getHottestAndColdestCityInCountry +} from '$lib/db/queries'; import type { CityBasic, CityTemperature, CountryBasic, HotColdCity } from '$lib/types'; import { type Actions } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; @@ -6,74 +11,67 @@ import type { PageServerLoad } from './$types'; // import { fetchAllCitiesTemperatures } from '$lib/server/meteoDataHandler'; const errorObject = { - ok: false, - data: null, - error: 'Failed to fetch temperature' -} + ok: false, + data: null, + error: 'Failed to fetch temperature' +}; export const load: PageServerLoad = async ({ url }) => { - try { - const countries = allCountries.all() as CountryBasic[]; - const cities = allCities.all() as CityBasic[]; - const country_id = url.searchParams.get('country'); - // console.log('country_id >>> ', country_id); + try { + const countries = allCountries; + const cities = allCities; + const country_id = url.searchParams.get('country'); - let temperatureData: HotColdCity[] | null = null; - if (country_id) { - temperatureData = getHottestAndColdestCityInCountry.all({ - $country_id: Number(country_id), - $date: new Date().toISOString().split('T')[0] - }) as HotColdCity[]; - } + let temperatureData: HotColdCity[] | null = null; + if (country_id) { + temperatureData = (await getHottestAndColdestCityInCountry({ + country_id: Number(country_id), + date: new Date().toISOString().split('T')[0] + })) as HotColdCity[]; + } - // console.log('Loading >>>>', temperatureData); - - return { - countries, - cities, - temperatureData, - country_id: country_id ? parseInt(country_id) : null - }; - } catch (error) { - console.log(error); - return { - countries: [], - cities: [], - temperatureData: null, - country_id: null - }; - } + return { + countries, + cities, + temperatureData, + country_id: country_id ? parseInt(country_id) : null + }; + } catch (error) { + console.log(error); + return { + countries: [], + cities: [], + temperatureData: null, + country_id: null + }; + } }; export const actions: Actions = { - fetchTemperature: async ({ request }) => { - try { - const formData = await request.formData(); - const country_id = formData.get('country_id') as string; + fetchTemperature: async ({ request }) => { + try { + const formData = await request.formData(); + const country_id = formData.get('country_id') as string; - // console.log('FORM ACTION >>>', country_id) - const response = getHottestAndColdestCityInCountry.all({ - $country_id: Number(country_id), - $date: new Date().toISOString().split('T')[0] - }) as HotColdCity[]; + // console.log('FORM ACTION >>>', country_id) + const response = (await getHottestAndColdestCityInCountry({ + country_id: Number(country_id), + date: new Date().toISOString().split('T')[0] + })) as HotColdCity[]; - if (response.length == 2) { - // console.log('RESPONSE >>>>', response); - - return { - ok: true, - data: response, - error: null, - country_id: country_id ?? null - }; - } else { - return errorObject; - } - - } catch (error) { - console.log(error); - return errorObject; - } - - } -} + if (response.length == 2) { + return { + ok: true, + data: response, + error: null, + country_id: country_id ?? null + }; + } else { + return errorObject; + } + } catch (error) { + console.log(error); + return errorObject; + } + } +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index d9725cf..d58bf87 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -20,15 +20,15 @@ let temperatureData: HotColdCity[] | null = $state(data.temperatureData); let tempDiff: number | null = $derived( temperatureData - ? Number(Math.abs(temperatureData[0].max_temp - temperatureData[1].max_temp).toFixed(2)) + ? Number(Math.abs(temperatureData[0]?.max_temp - temperatureData[1]?.max_temp).toFixed(2)) : null ); let tempDiffImperial: number | null = $derived( temperatureData ? Number( Math.abs( - celsiusToFahrenheit(temperatureData[0].max_temp) - - celsiusToFahrenheit(temperatureData[1].max_temp) + celsiusToFahrenheit(temperatureData[0]?.max_temp) - + celsiusToFahrenheit(temperatureData[1]?.max_temp) ).toFixed(2) ) : null @@ -58,8 +58,6 @@ temperatureData = form.data; } }); - - $effect(() => console.log(tempDiff)); @@ -109,7 +107,7 @@ {#if selectedCountryId && temperatureData}
- {#each temperatureData as temperature} + {#each temperatureData as temperature, index}
{#if temperature.coldest_rank === 1}
❄️ Coldest City