Use Postgres instead of SQLite

This commit is contained in:
Denis Donici 2025-03-18 00:37:28 +02:00
parent 42999604f6
commit b095ab6661
9 changed files with 364 additions and 315 deletions

7
.env.example Normal file
View File

@ -0,0 +1,7 @@
# DB_DATABASE = "./world.db"
ORIGIN=http://localhost:3000
PGHOST=localhost
PGPORT=5432
PGUSERNAME=user
PGPASSWORD=password
PGDATABASE=pg_db

Binary file not shown.

View File

@ -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;

View File

@ -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
// `;

View File

@ -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
};
}
};

View File

@ -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
};
}
}
}
};

View File

@ -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',

View File

@ -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;
}
}
}
};

View File

@ -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>