Compare commits
27 Commits
8f8f26493d
...
renovate/p
| Author | SHA1 | Date | |
|---|---|---|---|
|
17f6b899ab
|
|||
| d171292dd2 | |||
| f52d285013 | |||
|
a79f53e90c
|
|||
| 5ad7e33c8a | |||
|
87f266a3e2
|
|||
| dc039046fe | |||
|
9c53f37b39
|
|||
| 093e1e2ccb | |||
| 7a77f0d2d2 | |||
| e29631c4af | |||
| 31aad5511f | |||
| 976bc0c413 | |||
| 0a2979ecfe | |||
| c3e4519682 | |||
| d9833e1c27 | |||
| 19e80809c1 | |||
| 00ef91b644 | |||
| 7f7f710fe8 | |||
| 1573331f87 | |||
| 14f7bdc024 | |||
| 0b116a05df | |||
|
849ca78598
|
|||
| 8377aefaf7 | |||
| 3f5682f80c | |||
| ae84560ddd | |||
|
1f7253d954
|
@@ -50,3 +50,38 @@ jobs:
|
|||||||
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
|
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
|
||||||
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
|
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
|
||||||
image: true
|
image: true
|
||||||
|
|
||||||
|
guarddog:
|
||||||
|
runs-on: ubuntu-js
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v6
|
||||||
|
with:
|
||||||
|
python-version: '3.14'
|
||||||
|
|
||||||
|
- name: Install GuardDog
|
||||||
|
run: |
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
python3 -m pip install guarddog
|
||||||
|
|
||||||
|
- name: Run GuardDog
|
||||||
|
run: |
|
||||||
|
guarddog npm scan ./
|
||||||
|
|
||||||
|
- name: ntfy Failed
|
||||||
|
uses: niniyas/ntfy-action@master
|
||||||
|
if: failure()
|
||||||
|
with:
|
||||||
|
url: '${{ secrets.NTFY_URL }}'
|
||||||
|
topic: '${{ secrets.NTFY_TOPIC }}'
|
||||||
|
title: 'Security Failure - Site Profile'
|
||||||
|
priority: 4
|
||||||
|
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
|
||||||
|
tags: action,failed
|
||||||
|
details: 'Guarddog scan failed for Site Profile'
|
||||||
|
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
|
||||||
|
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
|
||||||
|
image: true
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ ENV SITE_URL=https://www.alexlebens.dev
|
|||||||
ENV DIRECTUS_URL=https://directus.alexlebens.net
|
ENV DIRECTUS_URL=https://directus.alexlebens.net
|
||||||
ENV PORT=4321
|
ENV PORT=4321
|
||||||
|
|
||||||
LABEL version="2.5.0"
|
LABEL version="2.7.0"
|
||||||
LABEL description="Astro based personal website"
|
LABEL description="Astro based personal website"
|
||||||
|
|
||||||
EXPOSE $PORT
|
EXPOSE $PORT
|
||||||
|
|||||||
12
package.json
12
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "site-profile",
|
"name": "site-profile",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "2.5.0",
|
"version": "2.7.0",
|
||||||
"homepage": "https://www.alexlebens.dev",
|
"homepage": "https://www.alexlebens.dev",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
|
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@astrojs/check": "^0.9.6",
|
"@astrojs/check": "^0.9.6",
|
||||||
"@astrojs/node": "^9.5.2",
|
"@astrojs/node": "^9.5.3",
|
||||||
"@astrojs/partytown": "^2.1.4",
|
"@astrojs/partytown": "^2.1.4",
|
||||||
"@astrojs/react": "^4.4.2",
|
"@astrojs/react": "^4.4.2",
|
||||||
"@astrojs/rss": "^4.0.15",
|
"@astrojs/rss": "^4.0.15",
|
||||||
@@ -42,12 +42,12 @@
|
|||||||
"@swup/astro": "^1.7.0",
|
"@swup/astro": "^1.7.0",
|
||||||
"@tailwindcss/postcss": "^4.1.18",
|
"@tailwindcss/postcss": "^4.1.18",
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
"@types/react": "^19.2.13",
|
"@types/react": "^19.2.14",
|
||||||
"@types/unist": "^3.0.3",
|
"@types/unist": "^3.0.3",
|
||||||
"astro": "^5.17.1",
|
"astro": "^5.17.2",
|
||||||
"astro-compressor": "^1.2.0",
|
"astro-compressor": "^1.2.0",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"marked": "^17.0.1",
|
"marked": "^17.0.2",
|
||||||
"marked-shiki": "^1.2.1",
|
"marked-shiki": "^1.2.1",
|
||||||
"mdast-util-to-string": "^4.0.0",
|
"mdast-util-to-string": "^4.0.0",
|
||||||
"motion": "^12.34.0",
|
"motion": "^12.34.0",
|
||||||
@@ -62,7 +62,7 @@
|
|||||||
"ultrahtml": "^1.6.0"
|
"ultrahtml": "^1.6.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint-react/eslint-plugin": "^2.12.2",
|
"@eslint-react/eslint-plugin": "^2.12.4",
|
||||||
"@tailwindcss/forms": "^0.5.11",
|
"@tailwindcss/forms": "^0.5.11",
|
||||||
"@tailwindcss/typography": "^0.5.19",
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
|
|||||||
554
pnpm-lock.yaml
generated
554
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -108,7 +108,23 @@ const currentYear = new Date().getFullYear();
|
|||||||
© {currentYear} All rights reserved.
|
© {currentYear} All rights reserved.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center">
|
||||||
|
<p class="text-xs text-neutral-500 dark:text-neutral-400">
|
||||||
|
Weather provided by
|
||||||
|
</p>
|
||||||
|
<a
|
||||||
|
href="https://open-meteo.com/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
|
||||||
|
>
|
||||||
|
<span class="relative ml-1">
|
||||||
|
Open-Meteo.
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="ml-4"></div>
|
||||||
|
|
||||||
<span class="text-xs text-neutral-500 dark:text-neutral-400">Built with </span>
|
<span class="text-xs text-neutral-500 dark:text-neutral-400">Built with </span>
|
||||||
<a
|
<a
|
||||||
href="https://astro.build"
|
href="https://astro.build"
|
||||||
@@ -116,24 +132,8 @@ const currentYear = new Date().getFullYear();
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
|
class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100"
|
||||||
>
|
>
|
||||||
<svg class="mr-1 h-4 w-4 text-[#FF5D01]" viewBox="0 0 36 36" fill="none">
|
<span class="relative ml-1">
|
||||||
<path
|
Astro.
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M8.833 22.958c.622-1.185 1.832-1.918 3.18-1.918 2.292 0 4.145 1.86 4.145 4.153 0 1.34-.626 2.54-1.601 3.303 1.223-1.299 1.97-3.048 1.97-4.971 0-3.994-3.243-7.233-7.242-7.233-2.818 0-5.26 1.6-6.469 3.933.78-2.912 3.428-5.06 6.577-5.06 3.75 0 6.79 3.035 6.79 6.78 0 2.606-1.468 4.868-3.616 6.002a4.163 4.163 0 0 0 2.285-3.724c0-2.293-1.853-4.153-4.145-4.153-1.348 0-2.558.733-3.18 1.918l1.306-3.03Z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
<path
|
|
||||||
fill-rule="evenodd"
|
|
||||||
clip-rule="evenodd"
|
|
||||||
d="M22.155 12.056c-.622 1.185-1.832 1.918-3.18 1.918-2.292 0-4.145-1.86-4.145-4.153 0-1.34.626-2.54 1.601-3.303-1.223 1.299-1.97 3.048-1.97 4.971 0 3.994 3.243 7.233 7.242 7.233 2.818 0 5.26-1.6 6.469-3.933-.78 2.912-3.428 5.06-6.577 5.06-3.75 0-6.79-3.035-6.79-6.78 0-2.606 1.468-4.868 3.616-6.002a4.163 4.163 0 0 0-2.285 3.724c0 2.293 1.853 4.153 4.145 4.153 1.348 0 2.558-.733 3.18-1.918l-1.306 3.03Z"
|
|
||||||
fill="currentColor"></path>
|
|
||||||
</svg>
|
|
||||||
<span class="relative">
|
|
||||||
Astro
|
|
||||||
<span
|
|
||||||
class="absolute bottom-0 left-0 h-0.5 w-0 bg-[#FF5D01] transition-all duration-300 group-hover:w-full"
|
|
||||||
>
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
---
|
---
|
||||||
import { getFiveDayForecast } from '@support/weather';
|
import { getFiveDayForecast } from '@support/weather';
|
||||||
|
|
||||||
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota" } = Astro.props;
|
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
|
||||||
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude);
|
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone);
|
||||||
|
|
||||||
const borderClasses = 'border border-neutral-100 dark:border-stone-500/20';
|
const borderClasses = 'border border-neutral-100 dark:border-stone-500/20';
|
||||||
const bgColorClasses = 'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
|
const bgColorClasses = 'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
|
||||||
@@ -16,7 +16,7 @@ const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shado
|
|||||||
</h1>
|
</h1>
|
||||||
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
|
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
|
||||||
<p class="text-lg text-pretty text-neutral-600 dark:text-neutral-400">
|
<p class="text-lg text-pretty text-neutral-600 dark:text-neutral-400">
|
||||||
5 day forecast for {cityName}
|
Five day forecast for {cityName}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export type Weather = {
|
|||||||
location: string;
|
location: string;
|
||||||
latitude: string;
|
latitude: string;
|
||||||
longitude: string;
|
longitude: string;
|
||||||
|
timezone: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Post = {
|
export type Post = {
|
||||||
|
|||||||
@@ -45,10 +45,13 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
<FeaturesSection />
|
<FeaturesSection />
|
||||||
|
|
||||||
<WeatherSection
|
<WeatherSection
|
||||||
|
server:defer
|
||||||
latitude={weather.latitude}
|
latitude={weather.latitude}
|
||||||
longitude={weather.longitude}
|
longitude={weather.longitude}
|
||||||
cityName={weather.location}
|
cityName={weather.location}
|
||||||
/>
|
timezone={weather.timezone}
|
||||||
|
>
|
||||||
|
</WeatherSection>
|
||||||
|
|
||||||
<LatestPosts />
|
<LatestPosts />
|
||||||
|
|
||||||
@@ -109,5 +112,11 @@ const weather = await directus.request(readSingleton('site_weather'));
|
|||||||
};
|
};
|
||||||
|
|
||||||
animateContent();
|
animateContent();
|
||||||
|
|
||||||
|
const observer = new MutationObserver(() => {
|
||||||
|
animateContent();
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, { childList: true, subtree: true });
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -28,40 +28,38 @@ const getWeatherInfo = (code: number) => {
|
|||||||
return { label: 'Unknown', icon: '03d' };
|
return { label: 'Unknown', icon: '03d' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDayName = (dateStr: string) => {
|
export const getDayName = (dateStr: string) => {
|
||||||
return new Date(dateStr).toLocaleDateString('en-US', { weekday: 'short' });
|
const date = new Date(`${dateStr}T00:00:00`);
|
||||||
|
return date.toLocaleDateString('en-US', { weekday: 'short' });
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getFiveDayForecast(latitude: string, longitude: string): Promise<WeatherResult> {
|
async function getFiveDayForecast(latitude: string, longitude: string, timezone: string): Promise<WeatherResult> {
|
||||||
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weather_code,temperature_2m_max&timezone=auto&temperature_unit=fahrenheit`;
|
const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&daily=weather_code,temperature_2m_max&timezone=${timezone}&temperature_unit=fahrenheit`;
|
||||||
|
let data: any;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) throw new Error("Weather service unavailable");
|
if (!response.ok) throw new Error("Weather service unavailable");
|
||||||
|
data = await response.json();
|
||||||
|
|
||||||
const data = await response.json();
|
} catch (e: unknown) {
|
||||||
|
return { forecastDays: [], error: "Failed to load weather" };
|
||||||
const forecastDays = data.daily.time.map((date: string, index: number): DayForecast => {
|
}
|
||||||
const code = data.daily.weather_code[index];
|
|
||||||
const info = getWeatherInfo(code);
|
|
||||||
|
|
||||||
|
const forecastDays = data.daily.time
|
||||||
|
.slice(0, 5)
|
||||||
|
.map((date: string, index: number): DayForecast => {
|
||||||
return {
|
return {
|
||||||
date,
|
date,
|
||||||
temp: Math.round(data.daily.temperature_2m_max[index]),
|
temp: Math.round(data.daily.temperature_2m_max[index]),
|
||||||
code,
|
code: data.daily.weather_code[index],
|
||||||
label: info.label,
|
label: getWeatherInfo(data.daily.weather_code[index]).label,
|
||||||
icon: info.icon,
|
icon: getWeatherInfo(data.daily.weather_code[index]).icon,
|
||||||
dayName: getDayName(date)
|
dayName: getDayName(date)
|
||||||
};
|
};
|
||||||
}).slice(0, 5);
|
});
|
||||||
|
|
||||||
return { forecastDays, error: null };
|
return { forecastDays, error: null };
|
||||||
} catch (e: unknown) {
|
|
||||||
return {
|
|
||||||
forecastDays: [],
|
|
||||||
error: e instanceof Error ? e.message : "An unexpected error occurred"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export { getFiveDayForecast };
|
export { getFiveDayForecast };
|
||||||
|
|||||||
Reference in New Issue
Block a user