Skip to content
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
*DS_Store

# files with potential sensitive information
**/.env
**/.env

# go-task caching
.task/
3 changes: 0 additions & 3 deletions api/GetQuote/sample.dat

This file was deleted.

46 changes: 46 additions & 0 deletions api/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Overview

This directory contains the APIs of the static web app. Each API is implemented as a managed function.
For more information, see the [Static Web Apps API documentation](https://learn.microsoft.com/en-us/azure/static-web-apps/apis-overview).

## Getting Started

To run the APIs locally, you need to have the [Azure Static Web Apps CLI](https://learn.microsoft.com/en-us/azure/static-web-apps/local-development) installed.

Setup local development configuration by adding a `api/local.settings.json` file in this directory with the following content (example):

```json
{
"IsEncrypted": false,
"Values": {
"FUNCTIONS_WORKER_RUNTIME": "node",
"AzureWebJobsStorage": "UseDevelopmentStorage=true"
}
}
```

After setting things up, you can start the local development server by running the following command in the root directory of the project:

```bash
task run-devserver
```

There are two main ways to run the project locally:

### `task run-devserver`

- Provides **live reloading** when you save files
- Uses Astro's development server for fast iteration
- **Limitation**: API endpoints don't work (known issue with SWA CLI)
- **Best for**: Frontend-only development

### `task run`

- Builds a static version of the entire app
- Spins up both the web app AND function APIs together
- **API endpoints work correctly**
- **Limitation**: No live reloading - requires rebuilding to see changes
- **Best for**: Testing the complete application including API functionality

> [!TIP]
> Choose `task run-devserver` for rapid frontend development, or `task run` when you need to test API endpoints and the full integrated experience.
4 changes: 2 additions & 2 deletions api/host.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[3.*, 4.0.0)"
"version": "[4.*, 5.0.0)"
}
}
}
6 changes: 4 additions & 2 deletions api/GetQuote/function.json → api/quote/function.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get"]
"methods": [
"get"
]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
],
"scriptFile": "../dist/GetQuote/index.js"
"scriptFile": "../dist/quote/index.js"
}
68 changes: 35 additions & 33 deletions api/GetQuote/index.ts → api/quote/index.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,69 @@
import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { AzureFunction, Context, HttpRequest } from "@azure/functions"

interface IQoute {
id: string;
content: string;
author: string;
authorSlug: string;
length: number;
tags: string[];
creationdate: Date;
id: string
content: string
author: string
authorSlug: string
length: number
tags: string[]
creationdate: Date
}

const httpTrigger: AzureFunction = async function (
context: Context,
req: HttpRequest
): Promise<void> {
context.log("HTTP trigger function processed a request.");
console.log("Received request: ", req.headers.host);
context.log("HTTP trigger function processed a request.")
console.log("Received request: ", req.headers.host)

console.log("Check presence of environment variables.");
const api_url = process.env.API_URL;
console.log("Check presence of environment variable API_URL")
const api_url = process.env.API_URL
if (!api_url) {
console.error("Environment variable API_URL is not set.");
console.error("Environment variable API_URL is not set.")
context.res = {
status: 500,
body: "Internal Server Error",
};
return;
}
return
}

const api_key = process.env.API_KEY;
console.log("Check presence of environment variable API_KEY")
const api_key = process.env.API_KEY
if (!api_key) {
console.error("Environment variable API_KEY is not set.");
console.error("Environment variable API_KEY is not set.")
context.res = {
status: 500,
body: "Internal Server Error",
};
return;
}
return
}

const headers = {
// Creating the API request
const headers = new Headers({
"Content-Type": "application/json",
Accept: "application/json",
"Ocp-Apim-Subscription-Key": api_key,
};
})

try {
console.log("Fetching quote from API.");
console.log("Fetching quote from API.")
const response = await fetch(api_url, {
method: "GET",
headers: headers,
});
})

if (!response.headers.get("Content-Type")?.includes("application/json")) {
console.error("API did not return JSON response.");
console.error("API did not return JSON response.")
context.res = {
status: 500,
body: "Internal Server Error",
};
return;
}
return
}

const respJson = response.json();
const quote = (await respJson) as IQoute;
const respJson = response.json()
const quote = (await respJson) as IQoute

context.res = {
Headers: {
Expand All @@ -74,15 +76,15 @@ const httpTrigger: AzureFunction = async function (
author: quote.author,
creationdate: quote.creationdate,
},
};
}
} catch (err) {
console.error("Failed to call API: ", err);
console.error("Failed to call API: ", err)

context.res = {
status: 500,
body: "Internal Server Error",
};
}
}
};
}

export default httpTrigger;
export default httpTrigger
19 changes: 19 additions & 0 deletions swa/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions swa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
"astro": "^5.16.2",
"remark-github-blockquote-alert": "^2.0.1",
"typescript": "^5.3.3"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.6"
}
}
2 changes: 1 addition & 1 deletion swa/src/components/Card.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface Props {
href: string;
}

const { href, title, body } = Astro.props;
const { title, body, href } = Astro.props;
---

<style>
Expand Down
2 changes: 1 addition & 1 deletion swa/src/pages/404.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
import Layout from '../layouts/Layout.astro';
import Layout from '@layouts/Layout.astro';
---

<Layout title="Page Not Found.">
Expand Down
17 changes: 9 additions & 8 deletions swa/src/pages/index.astro
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
---
import Layout from '../layouts/Layout.astro';
import CardGrid from '../components/CardGrid.astro';
import SocialBubble from '../components/SocialBubble.astro';
import Layout from '@layouts/Layout.astro';
import CardGrid from '@components/CardGrid.astro';
import SocialBubble from '@components/SocialBubble.astro';
// add font awesome icons
import { faGithub, faPaypal, faStackOverflow } from '@fortawesome/free-brands-svg-icons';
// import the Image component and the image
import { Image } from 'astro:assets';
import profilePicture from '../images/profile-picture.jpg';
// import custom scripts
import loadRepositories from '../scripts/loadRepositories.js';
import loadGistsOfPastYear from '../scripts/loadGistsOfPastYear.js';
import loadRepositories from '@scripts/loadRepositories.ts';
import loadGistsSinceYear from '@scripts/loadGistsSinceYear.ts';

const repos = await loadRepositories(['clowa.dev', 'my-setup', 'docker-terraform', 'docker-powershell-core', 'arduino-plant-watering', 'golang-custom-rpi-exporter']);
const gists = await loadGistsOfPastYear();
const gists = await loadGistsSinceYear(2025);

// Load blog posts dynamically from the posts directory
const postFiles = import.meta.glob('./posts/*.md', { eager: true });
Expand Down Expand Up @@ -82,15 +82,16 @@ const publishedPosts = allPosts
<hr class="divider" />
<CardGrid
title="My recent GitHub Gists"
cards={gists.map((gist: { id: any; description: string; html_url: string }) => ({
cards={gists.map(gist => ({
title: "",
body: gist.description,
href: gist.html_url
}))}
/>
</main>
</Layout>

<script src="../scripts/loadQuote.js"></script>
<script src="@scripts/loadQuote.ts"></script>

<style>
/* General */
Expand Down
2 changes: 1 addition & 1 deletion swa/src/pages/posts/home-assistant-host-ssh-access.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
layout: '../../layouts/BlogPost.astro'
layout: '@layouts/BlogPost.astro'
title: 'Accessing Home Assistant via SSH'
draft: false
pubDate: 2025-12-17
Expand Down
31 changes: 0 additions & 31 deletions swa/src/scripts/loadGistsOfPastYear.js

This file was deleted.

40 changes: 40 additions & 0 deletions swa/src/scripts/loadGistsSinceYear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
interface Gist {
id: any
description: string
html_url: string
}

async function loadGistsSinceYear(year: number, month: number = 0, day: number = 1): Promise<Gist[]> {
const headers = new Headers()
headers.set("Accept", "application/vnd.github+json")
headers.set("X-GitHub-Api-Version", "2022-11-28")

if (process.env.GITHUB_TOKEN) {
headers.set("Authorization", `Bearer ${process.env.GITHUB_TOKEN}`)
}

// Get ISO 8601 formatted date string for the year
const yearString = new Date(new Date().setFullYear(year, month, day)).toISOString()
const url = `https://api.github.com/users/clowa/gists?since=${yearString}`

console.log(`Fetching gists since: ${yearString} from URL: ${url}`)

const response = await fetch(url, {
headers: headers,
})

if (!response.ok) {
throw new Error(`Network response was not ok ${response.statusText}`)
}

const gists: Gist[] = await response.json()

// Print properties description and html_url for gists to console
gists.forEach((gist: Gist) => {
console.log(gist.description, gist.html_url)
})

return gists
}

export default loadGistsSinceYear
Loading