Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use yr.no as weather source #354

Merged
merged 2 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,6 @@ env:
UPSTASH_REDIS_REST_URL: ${{secrets.UPSTASH_REDIS_REST_URL}}
UPSTASH_REDIS_REST_TOKEN: ${{secrets.UPSTASH_REDIS_REST_TOKEN}}

# Openweather
OPENWEATHER_API_KEY: ${{secrets.OPENWEATHER_API_KEY}}

jobs:
main:
name: CI
Expand Down
8 changes: 4 additions & 4 deletions src/app/(dashboard)/app/_components/weather.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ export const WeatherData: FC = () => {

return (
<span className="text-tiny text-default-500">
You can expect a 👆 high of {weatherData.main.temp_max.toFixed()}º and a
👇 low of {weatherData.main.temp_min.toFixed()}º with{" "}
{getFormattedWeatherDescription(weatherData.weather[0]?.description)}{" "}
today.
You can expect a 👆 high of {weatherData.temp_max.toFixed()}º and a 👇 low
of {weatherData.temp_min.toFixed()}º
{getFormattedWeatherDescription(weatherData.summary)} for today&apos;s
weather.
</span>
);
};
2 changes: 0 additions & 2 deletions src/env.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ export const env = createEnv({
UPSTASH_REDIS_REST_URL: z.string().optional(),
UPSTASH_REDIS_REST_TOKEN: z.string().optional(),
CLERK_SECRET_KEY: z.string().min(1),
OPENWEATHER_API_KEY: z.string().min(1),
},
client: {
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
Expand All @@ -26,7 +25,6 @@ export const env = createEnv({
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
UPSTASH_REDIS_REST_TOKEN: process.env.UPSTASH_REDIS_REST_TOKEN,
UPSTASH_REDIS_REST_URL: process.env.UPSTASH_REDIS_REST_URL,
OPENWEATHER_API_KEY: process.env.OPENWEATHER_API_KEY,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
});
62 changes: 50 additions & 12 deletions src/server/api/routers/weather.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { TRPCError } from "@trpc/server";
import fetch from "node-fetch";
import { z } from "zod";

import { env } from "@/env.mjs";

import { createTRPCRouter, protectedProcedure } from "../trpc";

type RawWeatherData = {
properties: {
timeseries: {
data: {
next_12_hours: {
summary: {
symbol_code: string;
}
},
instant: {
details: {
air_temperature: number;
}
}
}
}[]
}
}

type WeatherData = {
main: {
temp_max: number;
temp_min: number;
};
weather: {
description: string;
}[];
temp_max: number;
temp_min: number;
summary: string;
};

export const weatherRouter = createTRPCRouter({
Expand All @@ -28,7 +40,12 @@ export const weatherRouter = createTRPCRouter({
const { latitude, longitude } = input;

const response = await fetch(
`https://api.openweathermap.org/data/2.5/weather?lat=${latitude}&lon=${longitude}&appid=${env.OPENWEATHER_API_KEY}&units=metric`,
`https://api.met.no/weatherapi/locationforecast/2.0/compact?lat=${latitude}&lon=${longitude}`,
{
headers: {
"User-Agent": `noodle.run (https://github.com/noodle-run/noodle)`
}
}
);

if (!response.ok) {
Expand All @@ -38,6 +55,27 @@ export const weatherRouter = createTRPCRouter({
});
}

return response.json() as Promise<WeatherData>;
const rawWeatherData: RawWeatherData = await response.json() as RawWeatherData;

if (rawWeatherData.properties.timeseries.length < 12) {
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message: "Partial weather data"
})
}

const temperatures = [];

for (const timeseries of rawWeatherData.properties.timeseries) {
temperatures.push(timeseries.data.instant.details.air_temperature);
}

const weatherData: WeatherData = {
summary: rawWeatherData.properties.timeseries[0]!.data.next_12_hours.summary.symbol_code,
temp_max: Math.max(...temperatures),
temp_min: Math.min(...temperatures)
}

return weatherData;
}),
});
92 changes: 48 additions & 44 deletions src/utils/getFormattedWeatherDescription.ts
Original file line number Diff line number Diff line change
@@ -1,48 +1,52 @@
const weatherCodeToEnglish: Record<string, string> = {
clearsky: "🌞 a clear sky",
cloudy: "☁️ clouds",
fair: "⛅ fair",
fair_day: "🌤️ fair day",
fog: "🌫️ fog",
heavyrain: "🌧️ heavy rain",
heavyrainandthunder: "⛈️ heavy rain and thunder",
heavyrainshowers: "🌧️ heavy rain showers",
heavyrainshowersandthunder: "⛈️ heavy rain showers and thunder",
heavysleet: "🌨️ heavy sleet",
heavysleetandthunder: "⛈️ heavy sleet and thunder",
heavysleetshowers: "🌨️ heavy sleet showers",
heavysleetshowersandthunder: "⛈️ heavy sleet showers and thunder",
heavysnow: "❄️ heavy snow",
heavysnowandthunder: "⛈️ heavy snow and thunder",
heavysnowshowers: "❄️ heavy snow showers",
heavysnowshowersandthunder: "⛈️ heavy snow showers and thunder",
lightrain: "🌦️ light rain",
lightrainandthunder: "⛈️ light rain and thunder",
lightrainshowers: "🌦️ light rain showers",
lightrainshowersandthunder: "⛈️ light rain showers and thunder",
lightsleet: "🌧️ light sleet",
lightsleetandthunder: "⛈️ light sleet and thunder",
lightsleetshowers: "🌧️ light sleet showers",
lightsnow: "🌨️ light snow",
lightsnowandthunder: "⛈️ light snow and thunder",
lightsnowshowers: "🌨️ light snow showers",
lightssleetshowersandthunder: "⛈️ light sleet showers and thunder",
lightssnowshowersandthunder: "⛈️ light snow showers and thunder",
partlycloudy: "🌥️ some clouds",
rain: "🌧️ rain",
rainandthunder: "⛈️ rain and thunder",
rainshowers: "🌧️ rain showers",
rainshowersandthunder: "⛈️ rain showers and thunder",
sleet: "🌨️ sleet",
sleetandthunder: "⛈️ sleet and thunder",
sleetshowers: "🌨️ sleet showers",
sleetshowersandthunder: "⛈️ sleet showers and thunder",
snow: "❄️ snow",
snowandthunder: "⛈️ snow and thunder",
snowshowers: "❄️ snow showers",
snowshowersandthunder: "⛈️ snow showers and thunder",
};

export const getFormattedWeatherDescription = (
condition: string | undefined,
) => {
if (!condition) return;

const weatherToEmoji: Record<string, string> = {
"clear sky": "☀️",
"few clouds": "🌤️",
"scattered clouds": "⛅",
"broken clouds": "☁️",
"overcast clouds": "☁️",
rain: "🌧️",
"light rain": "🌧️",
"moderate rain": "🌧️",
"heavy intensity rain": "🌧️",
"very heavy rain": "🌧️",
"extreme rain": "🌧️",
"freezing rain": "🌨️",
"light intensity shower rain": "🌦️",
"shower rain": "🌧️",
"heavy intensity shower rain": "🌧️",
"ragged shower rain": "🌧️",
"light snow": "❄️",
snow: "❄️",
"heavy snow": "❄️",
sleet: "🌨️",
"shower sleet": "🌨️",
"light rain and snow": "🌨️",
"rain and snow": "🌨️",
"light shower snow": "🌨️",
"shower snow": "🌨️",
"heavy shower snow": "🌨️",
mist: "🌫️",
smoke: "🌫️",
haze: "🌫️",
"sand/ dust whirls": "🌪️",
fog: "🌫️",
sand: "🌫️",
dust: "🌫️",
"volcanic ash": "🌫️",
squalls: "🌬️",
tornado: "🌪️",
clear: "☀️",
clouds: "☁️",
};

return `${weatherToEmoji[condition.toLowerCase()] ?? ""} ${condition}`;
if (!condition || !weatherCodeToEnglish[condition]) return;
const weatherDescription = weatherCodeToEnglish[condition];
return `with ${weatherDescription}`;
};