mirror of
https://github.com/gevera/hot-cold-cities.git
synced 2025-12-06 07:08:20 +00:00
Use Postgres instead of SQLite
This commit is contained in:
parent
42999604f6
commit
b095ab6661
7
.env.example
Normal file
7
.env.example
Normal file
@ -0,0 +1,7 @@
|
||||
# DB_DATABASE = "./world.db"
|
||||
ORIGIN=http://localhost:3000
|
||||
PGHOST=localhost
|
||||
PGPORT=5432
|
||||
PGUSERNAME=user
|
||||
PGPASSWORD=password
|
||||
PGDATABASE=pg_db
|
||||
BIN
database/data.db
BIN
database/data.db
Binary file not shown.
@ -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;
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import db from ".";
|
||||
|
||||
// export const allCountries = db.query('SELECT id, name, native, emoji FROM countries');
|
||||
export const allCountries = db.query(`
|
||||
// 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(
|
||||
@ -9,119 +10,147 @@ export const allCountries = db.query(`
|
||||
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<CityBasic[]> =>
|
||||
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<CityTemperature | null> =>
|
||||
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<CityTemperaturSimple> =>
|
||||
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,
|
||||
ROUND(md.max::numeric, 2) 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
|
||||
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
|
||||
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
|
||||
)
|
||||
SELECT * FROM world_stats
|
||||
ORDER BY max_temp DESC
|
||||
LIMIT 2
|
||||
`);
|
||||
|
||||
`;
|
||||
|
||||
// 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
|
||||
// `;
|
||||
|
||||
@ -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 {
|
||||
|
||||
const cities = allCitiesWithNoTemperatureToday.all({ $date: date }) as CityBasic[];
|
||||
|
||||
const cities = await allCitiesWithNoTemperatureToday({ date });
|
||||
console.log('Today date: ', date);
|
||||
console.log('Total Cities to fetch: ', cities.length);
|
||||
|
||||
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
|
||||
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}`);
|
||||
}
|
||||
// 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}`);
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Finished fetching temperatures for all cities: ', date);
|
||||
return {
|
||||
ok: true,
|
||||
}
|
||||
ok: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch cities:', error);
|
||||
return {
|
||||
ok: false
|
||||
ok: false,
|
||||
error: error?.message // Added error message for better debugging
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
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";
|
||||
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);
|
||||
@ -11,28 +11,27 @@ export const getLocationTemperature = async (id: number, latitude: string, longi
|
||||
url.searchParams.set('format', 'json');
|
||||
url.searchParams.set('forecast_days', '1');
|
||||
|
||||
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
console.log('Fetching data from open meteo...');
|
||||
if (response.ok) {
|
||||
const data = await response.json() as MeteoResponseType;
|
||||
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],
|
||||
date: data.daily.time[0]
|
||||
} as MeteoData
|
||||
};
|
||||
} else {
|
||||
return { data: null };
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return {
|
||||
data: null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -19,7 +19,14 @@ export type CityTemperature = {
|
||||
min: number;
|
||||
max: number;
|
||||
date: string;
|
||||
}
|
||||
};
|
||||
|
||||
export type CityTemperaturSimple = {
|
||||
city_id: number;
|
||||
min: number;
|
||||
max: number;
|
||||
date: string;
|
||||
};
|
||||
|
||||
export type HotColdCity = {
|
||||
city_id: number;
|
||||
@ -30,14 +37,14 @@ export type HotColdCity = {
|
||||
date: string;
|
||||
hottest_rank: number;
|
||||
coldest_rank: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type ComboOption = {
|
||||
id: number;
|
||||
name: string;
|
||||
emoji?: string;
|
||||
country_id?: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type MeteoResponseType = {
|
||||
latitude: number;
|
||||
@ -51,20 +58,20 @@ export type MeteoResponseType = {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
export enum UnitsEnum {
|
||||
METRIC = 'METRIC',
|
||||
|
||||
@ -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';
|
||||
@ -9,25 +14,22 @@ const errorObject = {
|
||||
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 countries = allCountries;
|
||||
const cities = allCities;
|
||||
const country_id = url.searchParams.get('country');
|
||||
// console.log('country_id >>> ', country_id);
|
||||
|
||||
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[];
|
||||
temperatureData = (await getHottestAndColdestCityInCountry({
|
||||
country_id: Number(country_id),
|
||||
date: new Date().toISOString().split('T')[0]
|
||||
})) as HotColdCity[];
|
||||
}
|
||||
|
||||
// console.log('Loading >>>>', temperatureData);
|
||||
|
||||
return {
|
||||
countries,
|
||||
cities,
|
||||
@ -52,14 +54,12 @@ export const actions: Actions = {
|
||||
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[];
|
||||
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,
|
||||
@ -69,11 +69,9 @@ export const actions: Actions = {
|
||||
} else {
|
||||
return errorObject;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return errorObject;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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));
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
@ -109,7 +107,7 @@
|
||||
</form>
|
||||
{#if selectedCountryId && temperatureData}
|
||||
<div class="my-8 flex flex-col justify-evenly gap-12 md:flex-row">
|
||||
{#each temperatureData as temperature}
|
||||
{#each temperatureData as temperature, index}
|
||||
<dl class="flex flex-col gap-2">
|
||||
{#if temperature.coldest_rank === 1}
|
||||
<dt class="text-center text-lg text-blue-600">❄️ Coldest City</dt>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user