full upgrade (#31)

*  chore: update CI workflow to include testing and multi-arch build (#29)

*  chore: remove unused dependencies from package-lock.json and updated to a better local dev environment (#30)

*  test: add unit tests

*  test: add unit tests for whole project

*  feat: add whatwg-fetch for improved fetch support

*  chore: update Node.js version to 22 in workflow

*  refactor: update types and improve email handling tests

*  refactor: remove unused imports

*  fix: normalize image name to lowercase in workflows

*  fix: ensure Docker image names are consistently lowercase

*  chore: update

*  chore: update base URL to use secret variable

*  chore: update to login to ghcr

*  fix: add missing 'fi' to close if statement in workflow
This commit is contained in:
Denshooter
2025-02-16 16:36:21 +01:00
committed by GitHub
parent b4616234cf
commit 180b9aa9f8
35 changed files with 5499 additions and 1901 deletions

View File

@@ -1,10 +1,7 @@
import {type NextRequest, NextResponse} from "next/server";
import { type NextRequest, NextResponse } from "next/server";
import nodemailer from "nodemailer";
import SMTPTransport from "nodemailer/lib/smtp-transport";
import Mail from "nodemailer/lib/mailer";
import dotenv from "dotenv";
dotenv.config();
export async function POST(request: NextRequest) {
const body = (await request.json()) as {
@@ -12,7 +9,7 @@ export async function POST(request: NextRequest) {
name: string;
message: string;
};
const {email, name, message} = body;
const { email, name, message } = body;
const user = process.env.MY_EMAIL ?? "";
const pass = process.env.MY_PASSWORD ?? "";
@@ -20,8 +17,16 @@ export async function POST(request: NextRequest) {
if (!user || !pass) {
console.error("Missing email/password environment variables");
return NextResponse.json(
{error: "Internal server error"},
{status: 500},
{ error: "Missing EMAIL or PASSWORD" },
{ status: 500 },
);
}
if (!email || !name || !message) {
console.error("Invalid request body");
return NextResponse.json(
{ error: "Invalid request body" },
{ status: 400 },
);
}
@@ -61,9 +66,9 @@ export async function POST(request: NextRequest) {
try {
await sendMailPromise();
return NextResponse.json({message: "Email sent"});
return NextResponse.json({ message: "Email sent" });
} catch (err) {
console.error("Error sending email:", err);
return NextResponse.json({error: err}, {status: 500});
return NextResponse.json({ error: "Failed to send email" }, { status: 500 });
}
}

View File

@@ -1,26 +1,26 @@
import {NextResponse} from "next/server";
import { NextResponse } from "next/server";
export const runtime = "nodejs"; // Force Node runtime
const GHOST_API_URL = "http://192.168.179.31:2368";
const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368";
const GHOST_API_KEY = process.env.GHOST_API_KEY;
export async function GET() {
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`,
);
if (!response.ok) {
console.error(`Failed to fetch posts: ${response.statusText}`);
return NextResponse.json([]);
}
const posts = await response.json();
return NextResponse.json(posts);
} catch (error) {
console.error("Failed to fetch posts from Ghost:", error);
return NextResponse.json(
{error: "Failed to fetch projects"},
{status: 500},
);
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`,
);
if (!response.ok) {
console.error(`Failed to fetch posts: ${response.statusText}`);
return NextResponse.json([]);
}
const posts = await response.json();
return NextResponse.json(posts);
} catch (error) {
console.error("Failed to fetch posts from Ghost:", error);
return NextResponse.json(
{ error: "Failed to fetch projects" },
{ status: 500 },
);
}
}

View File

@@ -2,7 +2,7 @@ import { NextResponse } from "next/server";
export const runtime = "nodejs"; // Force Node runtime
const GHOST_API_URL = "http://big-bear-ghost:2368";
const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368";
const GHOST_API_KEY = process.env.GHOST_API_KEY;
export async function GET(request: Request) {

View File

@@ -1,34 +0,0 @@
import {NextResponse} from 'next/server';
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
export async function GET() {
try {
const projectsDirectory = path.join(process.cwd(), 'public/projects');
const filenames = fs.readdirSync(projectsDirectory);
const projects = filenames
.filter((filename) => {
const filePath = path.join(projectsDirectory, filename);
return fs.statSync(filePath).isFile();
})
.map((filename) => {
const filePath = path.join(projectsDirectory, filename);
const fileContents = fs.readFileSync(filePath, 'utf8');
const {data} = matter(fileContents);
return {
id: data.id,
title: data.title,
description: data.description,
slug: filename.replace('.md', ''),
};
});
return NextResponse.json(projects);
} catch (error) {
console.error("Failed to fetch projects:", error);
return NextResponse.json({error: 'Failed to fetch projects'}, {status: 500});
}
}

View File

@@ -1,87 +1,102 @@
import {NextResponse} from "next/server";
import { NextResponse } from "next/server";
interface Project {
slug: string;
updated_at?: string; // Optional timestamp for last modification
slug: string;
updated_at?: string; // Optional timestamp for last modification
}
interface ProjectsData {
posts: Project[];
posts: Project[];
}
export const dynamic = 'force-dynamic';
export const dynamic = "force-dynamic";
export const runtime = "nodejs"; // Force Node runtime
const GHOST_API_URL = "http://192.168.179.31:2368";
const GHOST_API_URL = process.env.GHOST_API_URL || "http://192.168.179.31:2368";
const GHOST_API_KEY = process.env.GHOST_API_KEY;
// Funktion, um die XML für die Sitemap zu generieren
function generateXml(sitemapRoutes: { url: string; lastModified: string }[]) {
const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>';
const urlsetOpen = '<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">';
const urlsetClose = '</urlset>';
const xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>';
const urlsetOpen =
'<urlset xmlns="https://www.sitemaps.org/schemas/sitemap/0.9">';
const urlsetClose = "</urlset>";
const urlEntries = sitemapRoutes
.map(
(route) => `
const urlEntries = sitemapRoutes
.map(
(route) => `
<url>
<loc>${route.url}</loc>
<lastmod>${route.lastModified}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>`
)
.join("");
</url>`,
)
.join("");
return `${xmlHeader}${urlsetOpen}${urlEntries}${urlsetClose}`;
return `${xmlHeader}${urlsetOpen}${urlEntries}${urlsetClose}`;
}
export async function GET() {
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://dki.one";
const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "https://dki.one";
// Statische Routen
const staticRoutes = [
{url: `${baseUrl}/`, lastModified: new Date().toISOString(), priority: 1, changeFreq: "weekly"},
{url: `${baseUrl}/legal-notice`, lastModified: new Date().toISOString(), priority: 0.5, changeFreq: "yearly"},
{url: `${baseUrl}/privacy-policy`, lastModified: new Date().toISOString(), priority: 0.5, changeFreq: "yearly"},
];
// Statische Routen
const staticRoutes = [
{
url: `${baseUrl}/`,
lastModified: new Date().toISOString(),
priority: 1,
changeFreq: "weekly",
},
{
url: `${baseUrl}/legal-notice`,
lastModified: new Date().toISOString(),
priority: 0.5,
changeFreq: "yearly",
},
{
url: `${baseUrl}/privacy-policy`,
lastModified: new Date().toISOString(),
priority: 0.5,
changeFreq: "yearly",
},
];
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`
);
if (!response.ok) {
console.error(`Failed to fetch posts: ${response.statusText}`);
return new NextResponse(generateXml(staticRoutes), {
headers: {"Content-Type": "application/xml"},
})
}
const projectsData = await response.json() as ProjectsData;
const projects = projectsData.posts;
// Dynamische Projekt-Routen generieren
const sitemapRoutes = projects.map((project) => {
const lastModified = project.updated_at || new Date().toISOString();
return {
url: `${baseUrl}/projects/${project.slug}`,
lastModified,
priority: 0.8,
changeFreq: "monthly",
};
});
const allRoutes = [...staticRoutes, ...sitemapRoutes];
// Rückgabe der Sitemap im XML-Format
return new NextResponse(generateXml(allRoutes), {
headers: {"Content-Type": "application/xml"},
});
} catch (error) {
console.error("Failed to fetch posts from Ghost:", error);
// Rückgabe der statischen Routen, falls Fehler auftritt
return new NextResponse(generateXml(staticRoutes), {
headers: {"Content-Type": "application/xml"},
});
try {
const response = await fetch(
`${GHOST_API_URL}/ghost/api/content/posts/?key=${GHOST_API_KEY}&limit=all`,
);
if (!response.ok) {
console.error(`Failed to fetch posts: ${response.statusText}`);
return new NextResponse(generateXml(staticRoutes), {
headers: { "Content-Type": "application/xml" },
});
}
const projectsData = (await response.json()) as ProjectsData;
const projects = projectsData.posts;
// Dynamische Projekt-Routen generieren
const sitemapRoutes = projects.map((project) => {
const lastModified = project.updated_at || new Date().toISOString();
return {
url: `${baseUrl}/projects/${project.slug}`,
lastModified,
priority: 0.8,
changeFreq: "monthly",
};
});
const allRoutes = [...staticRoutes, ...sitemapRoutes];
// Rückgabe der Sitemap im XML-Format
return new NextResponse(generateXml(allRoutes), {
headers: { "Content-Type": "application/xml" },
});
} catch (error) {
console.log("Failed to fetch posts from Ghost:", error);
// Rückgabe der statischen Routen, falls Fehler auftritt
return new NextResponse(generateXml(staticRoutes), {
headers: { "Content-Type": "application/xml" },
});
}
}