30 Commits

Author SHA1 Message Date
itsfinniii
4f3cc40041 Finish first part of creating the full list of routes 2026-03-15 12:04:28 +01:00
itsfinniii
ff811327bb Update pages sitemaps 2026-03-15 11:33:38 +01:00
itsfinniii
6e26af71f4 Turn function for converting data into web page into seperate function 2026-03-15 11:28:10 +01:00
itsfinniii
6dcac27de8 Add a method to get all web pages 2026-03-15 11:23:47 +01:00
Quinn Hegeman
c1b89c5823 Update sitemaps for albums 2026-03-08 22:38:18 +01:00
Quinn Hegeman
ad73ab5672 Add a function to get all photo albums 2026-03-08 17:24:39 +01:00
Quinn Hegeman
44437c766b Fix params.page for currentPage in per page blogs sitemap 2026-03-08 16:20:33 +01:00
Quinn Hegeman
a5bc93f4d6 Get all projects and update sitemaps for it immediatly as well 2026-03-08 16:20:21 +01:00
Quinn Hegeman
52d8102d67 Fix the sitemap indexes 2026-03-08 16:03:08 +01:00
Quinn Hegeman
8f2e3bcde1 Update blog sitemap 2026-03-08 15:49:52 +01:00
Quinn Hegeman
f5c25dea75 Add last modified timestamps for all settings within getAllSettings 2026-03-08 15:28:45 +01:00
Quinn Hegeman
4d220e1be7 Add last modified to blogs 2026-03-08 15:16:32 +01:00
Quinn Hegeman
3317926f61 Add function that looks for all blogs 2026-03-08 15:12:19 +01:00
Quinn Hegeman
4d56dce1af Trim the robots text 2026-03-08 13:09:46 +01:00
Quinn Hegeman
2411c6fdc8 Create robots file 2026-03-08 13:04:41 +01:00
Quinn Hegeman
a1b202686f Update sitemap entry to albums.xml 2026-03-08 12:02:52 +01:00
Quinn Hegeman
8f0ede76f8 Fix some > to /> in WebpageLayout 2026-03-07 21:17:57 +01:00
Quinn Hegeman
f971e84f17 Add theme-color to WebpageLayout 2026-03-07 21:14:56 +01:00
Quinn Hegeman
640097c072 Create RSS feed base 2026-03-07 21:12:33 +01:00
Quinn Hegeman
b62865cf04 Fix the layout for the index page during development 2026-03-07 21:03:24 +01:00
Quinn Hegeman
403f8146d9 Create more sitemaps for the categories 2026-03-07 21:00:47 +01:00
Quinn Hegeman
dc22676254 Create the first sitemaps 2026-03-07 20:52:23 +01:00
Quinn Hegeman
5ac9285248 Create WebpageLayout.astro 2026-03-07 20:22:35 +01:00
Quinn Hegeman
e23c077a05 Make a function to get all website settings 2026-03-07 19:22:06 +01:00
Quinn Hegeman
f914b7db1c Set up graphql for requests 2026-03-07 18:51:46 +01:00
Quinn Hegeman
770198bb5b Add env example with Directus 2026-03-07 17:20:28 +01:00
Quinn Hegeman
025a84b2ef Add @ path to tsconfig 2026-03-07 17:20:15 +01:00
Quinn Hegeman
5caf0424cc Create a function for creating a Directus client in Astro 2026-03-07 17:01:25 +01:00
Quinn Hegeman
3865b4b089 Add Astro environment types 2026-03-07 16:57:45 +01:00
231b8cddc3 Merge pull request 'Setup the Astro project' (#3) from astro/setup-project into master
Reviewed-on: #3
2026-03-07 15:39:41 +00:00
63 changed files with 3959 additions and 553 deletions

2
astro/.env.example Normal file
View File

@@ -0,0 +1,2 @@
DIRECTUS_URL="https://"
DIRECTUS_TOKEN=""

View File

@@ -1,14 +1,14 @@
// @ts-check
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import tailwindcss from '@tailwindcss/vite';
import graphql from '@rollup/plugin-graphql';
// https://astro.build/config
export default defineConfig({
integrations: [preact()],
vite: {
plugins: [tailwindcss()]
plugins: [tailwindcss(), graphql()]
}
});

1991
astro/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -11,11 +11,18 @@
"dependencies": {
"@astrojs/preact": "^4.1.3",
"@directus/sdk": "^21.2.0",
"@rollup/plugin-graphql": "^2.0.5",
"@tailwindcss/vite": "^4.2.1",
"astro": "^5.17.1",
"md5": "^2.3.0",
"mdast-util-to-string": "^4.0.0",
"minify-xml": "^4.5.2",
"preact": "^10.28.4",
"reading-time": "^1.5.0",
"tailwindcss": "^4.2.1"
"tailwindcss": "^4.2.1",
"tslib": "^2.8.1"
},
"devDependencies": {
"@types/md5": "^2.3.6"
}
}

View File

@@ -0,0 +1,72 @@
import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql';
import getBlogs from '@/graphql/blogs/getBlogs.graphql';
import { formatDate } from "@/lib/dates";
export async function getAllBlogs(settings: GlobalSettings): Promise<BlogPost[]> {
const client = await createDirectusConnection();
const result = await client.query(print(getBlogs), {
date: formatDate(new Date(), "%Y-%M-%D")
});
let blogs: BlogPost[] = [];
result["Blogs"].forEach((blogRecord: any) => {
let dates: string[] = [
settings.blog.lastModified.toISOString(),
settings.website.lastModified.toISOString(),
blogRecord["date_created"],
blogRecord["date_updated"],
blogRecord["search_engine"][0]["date_created"],
blogRecord["search_engine"][0]["date_updated"],
blogRecord["search_engine"][0]["thumbnail"]["created_on"]
];
const blog: BlogPost = {
lastModified: new Date(),
title: blogRecord["title"],
content: blogRecord["content"],
date: blogRecord["date"],
url: blogRecord["url"],
searchEngine: {
title: blogRecord["search_engine"][0]["title"],
description: blogRecord["search_engine"][0]["description"],
allowCrawlers: blogRecord["search_engine"][0]["allow_crawler"],
canonical: blogRecord["search_engine"][0]["canonical"],
priority: blogRecord["search_engine"][0]["priority"],
thumbnail: {
url: blogRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: blogRecord["search_engine"][0]["thumbnail"]["height"],
width: blogRecord["search_engine"][0]["thumbnail"]["width"]
}
},
tags: []
};
blogRecord["tags"].forEach((tagRecord: any) => {
blog["tags"].push({
text: tagRecord["Tags_id"]["text"],
code: tagRecord["Tags_id"]["code"],
color: tagRecord["Tags_id"]["color"]
});
dates.push(tagRecord["Tags_id"]["date_created"]);
dates.push(tagRecord["Tags_id"]["date_updated"]);
});
if (dates.filter(e => e !== null).length === 0) {
blog.lastModified = new Date();
}
else {
const sortedDates: string[] = dates.sort((a: string, b: string) => {
return new Date(b).getTime() - new Date(a).getTime();
});
blog.lastModified = new Date(sortedDates[0]);
}
blogs.push(blog);
});
return blogs;
}

View File

@@ -0,0 +1,322 @@
import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql';
import { formatDate } from "@/lib/dates";
import getAllPages from "@/graphql/pages/getAllPages.graphql";
export function dataToPage(pageRecord: any): WebPage {
let dates: string[] = [
pageRecord["date_created"],
pageRecord["date_updated"],
pageRecord["search_engine"][0]["date_created"],
pageRecord["search_engine"][0]["date_updated"]
];
const searchEngine = pageRecord["search_engine"][0];
let components: WebpageComponent[] = [];
pageRecord["components"].forEach((componentRecord: any) => {
const component = componentRecord["item"];
switch (componentRecord["item"]["__typename"]) {
case "Hero":
let heroComponent: HeroComponent = {
component: "Hero",
id: component["hero_id"],
title: component["hero_title"],
text: component["hero_text"],
backgroundImage: {
url: component["background_image"]["filename_disk"],
width: component["background_image"]["width"],
height: component["background_image"]["height"]
}
};
components.push(heroComponent);
dates.push(component["hero_created"]);
dates.push(component["hero_updated"]);
dates.push(component["background_image"]["created_on"]);
break;
case "Text_With_Side_Image":
let textWithImageComponent: TextWithImageComponent = {
component: "TextWithImage",
id: component["twsi_id"],
title: component["twsi_title"],
text: component["twsi_text"],
imageSide: component["twsi_image_side"],
image: {
url: component["image"]["filename_disk"],
width: component["image"]["width"],
height: component["image"]["height"]
}
};
components.push(textWithImageComponent);
dates.push(component["twsi_created"]);
dates.push(component["twsi_updated"]);
dates.push(component["image"]["created_on"]);
break;
case "Wall_Of_Text":
let wallOfTextComponent: WallOfTextComponent = {
component: "WallOfText",
id: component["wot_id"],
title: component["wot_title"],
text: component["wot_text"]
};
components.push(wallOfTextComponent);
dates.push(component["wot_created"]);
dates.push(component["wot_updated"]);
break;
case "Frequently_Asked_Questions":
let faqComponent: FrequentlyAskedQuestionsComponent = {
component: "FrequentlyAskedQuestions",
id: component["faq_id"],
title: component["faq_title"],
text: component["faq_text"],
questions: []
};
component["questions"].forEach((faqQuestionRecord: any) => {
faqComponent.questions.push({
id: faqQuestionRecord["id"],
question: faqQuestionRecord["question"],
answer: faqQuestionRecord["answer"]
});
dates.push(faqQuestionRecord["date_created"]);
dates.push(faqQuestionRecord["date_updated"]);
});
components.push(faqComponent);
dates.push(component["faq_created"]);
dates.push(component["faq_updated"]);
break;
case "Upcoming_Events":
let upcomingEventsComponent: UpcomingEventsComponent = {
component: "UpcomingEvents",
id: component["ue_id"],
title: component["ue_title"],
text: component["ue_text"],
events: []
};
component["events"].forEach((eventRecord: any) => {
upcomingEventsComponent.events.push({
id: eventRecord["id"],
title: eventRecord["title"],
description: eventRecord["description"],
location: eventRecord["location"],
mapLocation: [
eventRecord["map_location"]["coordinates"][1],
eventRecord["map_location"]["coordinates"][0]
],
startDate: eventRecord["start_date"],
endDate: eventRecord["end_date"]
});
dates.push(eventRecord["date_created"]);
dates.push(eventRecord["date_updated"]);
});
components.push(upcomingEventsComponent);
dates.push(component["ue_created"]);
dates.push(component["ue_updated"]);
break;
case "Equipment_Table":
let equipmentTableComponent: EquipmentTableComponent = {
component: "EquipmentTable",
id: component["et_id"],
title: component["et_title"],
text: component["et_text"],
items: []
};
component["items"].forEach((itemRecord: any) => {
equipmentTableComponent.items.push({
id: itemRecord["id"],
title: itemRecord["title"],
text: itemRecord["text"],
icon: {
url: itemRecord["icon"]["filename_disk"],
width: itemRecord["icon"]["width"],
height: itemRecord["icon"]["height"]
}
});
dates.push(itemRecord["date_created"]);
dates.push(itemRecord["date_updated"]);
dates.push(itemRecord["icon"]["created_on"]);
});
components.push(equipmentTableComponent);
dates.push(component["et_created"]);
dates.push(component["et_updated"]);
break;
case "Review_List":
let reviewsComponent: ReviewListComponent = {
component: "Reviews",
id: component["rl_id"],
title: component["rl_title"],
text: component["rl_text"],
reviews: []
};
component["reviews"].forEach((reviewRecord: any) => {
reviewsComponent.reviews.push({
id: reviewRecord["id"],
name: reviewRecord["name"],
review: reviewRecord["review"],
stars: reviewRecord["stars"],
date: reviewRecord["date"],
thumbnail: {
url: reviewRecord["thumbnail"]["filename_disk"],
width: reviewRecord["thumbnail"]["width"],
height: reviewRecord["thumbnail"]["height"]
}
});
dates.push(reviewRecord["date_created"]);
dates.push(reviewRecord["date_updated"]);
dates.push(reviewRecord["thumbnail"]["created_on"]);
});
components.push(reviewsComponent);
dates.push(component["rl_created"]);
dates.push(component["rl_updated"]);
break;
case "Contact":
let contactComponent: ContactComponent = {
component: "Contact",
id: component["c_id"],
title: component["c_title"],
text: component["c_text"],
methods: []
};
component["methods"].forEach((contactMethodRecord: any) => {
contactComponent.methods.push({
id: contactMethodRecord["id"],
title: contactMethodRecord["title"],
text: contactMethodRecord["text"],
color: contactMethodRecord["color"],
icon: {
url: contactMethodRecord["icon"]["filename_disk"],
width: contactMethodRecord["icon"]["width"],
height: contactMethodRecord["icon"]["height"]
}
});
dates.push(contactMethodRecord["date_created"]);
dates.push(contactMethodRecord["date_updated"]);
dates.push(contactMethodRecord["icon"]["created_on"]);
});
components.push(contactComponent);
dates.push(component["c_created"]);
dates.push(component["c_updated"]);
break;
case "Last_Blogs":
let lastBlogsComponent: LastBlogsComponent = {
component: "LastBlogs",
id: component["lb_id"],
title: component["lb_title"],
readMoreButtonText: component["lb_read_more_button_text"],
amount: component["lb_amount"]
};
components.push(lastBlogsComponent);
dates.push(component["lb_created"]);
dates.push(component["lb_updated"]);
break;
case "Last_Projects":
let lastProjectsComponent: LastProjectsComponent = {
component: "LastProjects",
id: component["lp_id"],
title: component["lp_title"],
readMoreButtonText: component["lp_read_more_button_text"],
amount: component["lp_amount"]
};
components.push(lastProjectsComponent);
dates.push(component["lp_created"]);
dates.push(component["lp_updated"]);
break;
case "Last_Galleries":
let lastGalleriesComponent: LastGalleriesComponent = {
component: "LastGalleries",
id: component["lg_id"],
title: component["lg_title"],
readMoreButtonText: component["lg_read_more_button_text"],
amount: component["lg_amount"]
};
components.push(lastGalleriesComponent);
dates.push(component["lg_created"]);
dates.push(component["lg_updated"]);
break;
default:
break;
}
});
let lastModified: Date;
if (dates.filter(e => e !== null).length === 0) {
lastModified = new Date();
}
else {
const sortedDates: string[] = dates.sort((a: string, b: string) => {
return new Date(b).getTime() - new Date(a).getTime();
});
lastModified = new Date(sortedDates[0]);
}
let page: WebPage = {
id: pageRecord["id"],
lastModified: lastModified,
url: pageRecord["url"],
searchEngine: {
title: searchEngine["title"],
description: searchEngine["description"],
canonical: searchEngine["canonical"],
allowCrawlers: searchEngine["allow_crawler"],
priority: searchEngine["priority"],
thumbnail: {
url: searchEngine["thumbnail"]["filename_disk"],
height: searchEngine["thumbnail"]["height"],
width: searchEngine["thumbnail"]["width"]
}
},
components: components
}
return page;
}
export async function getAllWebpages(): Promise<WebPage[]> {
const client = await createDirectusConnection();
const result = await client.query(print(getAllPages), {
date: formatDate(new Date(), "%Y-%M-%D")
});
let pages: WebPage[] = [];
result["Pages"].forEach((pageRecord: any) => {
pages.push(dataToPage(pageRecord));
});
return pages;
}

View File

@@ -0,0 +1,79 @@
import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql";
import getAlbums from '@/graphql/photos/getAlbums.graphql';
import { formatDate } from "@/lib/dates";
export async function getAllAlbums(settings: GlobalSettings): Promise<PhotoAlbum[]> {
const client = await createDirectusConnection();
const result = await client.query(print(getAlbums), {
date: formatDate(new Date(), "%Y-%M-%D")
});
let albums: PhotoAlbum[] = [];
result["Photo_Albums"].forEach((albumRecord: any) => {
let dates: string[] = [
settings.website.lastModified.toISOString(),
settings.photo.lastModified.toISOString(),
albumRecord["date_created"],
albumRecord["date_updated"],
albumRecord["thumbnail"]["created_on"],
];
const album: PhotoAlbum = {
title: albumRecord["title"],
description: albumRecord["description"],
url: albumRecord["url"],
startDate: albumRecord["start_date"],
endDate: albumRecord["end_date"],
location: albumRecord["location"],
category: {
title: albumRecord["category"][0]["Photo_Categories_id"]["title"],
url: albumRecord["category"][0]["Photo_Categories_id"]["url"],
thumbnail: {
url: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["filename_disk"],
height: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["height"],
width: albumRecord["category"][0]["Photo_Categories_id"]["thumbnail"]["width"]
}
},
thumbnail: {
url: albumRecord["thumbnail"]["filename_download"],
height: albumRecord["thumbnail"]["height"],
width: albumRecord["thumbnail"]["width"]
},
photos: [],
lastModified: new Date()
};
albumRecord["photos"].forEach((photoRecord: any) => {
album.photos.push({
id: photoRecord["id"],
photo: {
url: photoRecord["photo"]["filename_disk"],
width: photoRecord["photo"]["width"],
height: photoRecord["photo"]["height"]
},
text: photoRecord["text"]
});
dates.push(photoRecord["date_created"]);
dates.push(photoRecord["date_updated"]);
dates.push(photoRecord["photo"]["created_on"]);
});
if (dates.filter(e => e !== null).length === 0) {
album.lastModified = new Date();
}
else {
const sortedDates: string[] = dates.sort((a: string, b: string) => {
return new Date(b).getTime() - new Date(a).getTime();
});
album.lastModified = new Date(sortedDates[0]);
}
albums.push(album);
});
return albums;
}

View File

@@ -0,0 +1,73 @@
import { formatDate } from "@/lib/dates";
import { createDirectusConnection } from "@/lib/directus";
import { print } from "graphql";
import getProjects from '@/graphql/projects/getProjects.graphql';
export async function getAllProjects(settings: GlobalSettings): Promise<ProjectPost[]> {
const client = await createDirectusConnection();
const result = await client.query(print(getProjects), {
date: formatDate(new Date(), "%Y-%M-%D")
});
let projects: ProjectPost[] = [];
result["Projects"].forEach((projectRecord: any) => {
let dates: string[] = [
settings.project.lastModified.toISOString(),
settings.website.lastModified.toISOString(),
projectRecord["date_created"],
projectRecord["date_updated"],
projectRecord["search_engine"][0]["date_created"],
projectRecord["search_engine"][0]["date_updated"],
projectRecord["search_engine"][0]["thumbnail"]["created_on"]
];
const project: ProjectPost = {
lastModified: new Date(),
title: projectRecord["title"],
content: projectRecord["content"],
date: projectRecord["date"],
url: projectRecord["url"],
searchEngine: {
title: projectRecord["search_engine"][0]["title"],
description: projectRecord["search_engine"][0]["description"],
allowCrawlers: projectRecord["search_engine"][0]["allow_crawler"],
canonical: projectRecord["search_engine"][0]["canonical"],
priority: projectRecord["search_engine"][0]["priority"],
thumbnail: {
url: projectRecord["search_engine"][0]["thumbnail"]["filename_disk"],
height: projectRecord["search_engine"][0]["thumbnail"]["height"],
width: projectRecord["search_engine"][0]["thumbnail"]["width"]
}
},
tags: []
};
projectRecord["tags"].forEach((tagRecord: any) => {
project["tags"].push({
text: tagRecord["Tags_id"]["text"],
code: tagRecord["Tags_id"]["code"],
color: tagRecord["Tags_id"]["color"]
});
dates.push(tagRecord["Tags_id"]["date_created"]);
dates.push(tagRecord["Tags_id"]["date_updated"]);
});
if (dates.filter(e => e !== null).length === 0) {
project.lastModified = new Date();
}
else {
const sortedDates: string[] = dates.sort((a: string, b: string) => {
return new Date(b).getTime() - new Date(a).getTime();
});
project.lastModified = new Date(sortedDates[0]);
}
projects.push(project);
});
return projects;
}

View File

@@ -0,0 +1,15 @@
import { createDirectusConnection } from "@/lib/directus";
import { print } from 'graphql';
import getRobotsQuery from '@/graphql/settings/robots.graphql';
export async function getRobotsSettings(): Promise<RobotsSettings> {
const client = await createDirectusConnection();
const result = await client.query(print(getRobotsQuery));
const robotsResult = result["Robots"];
return {
crawlers: robotsResult["crawlers"],
extraContent: robotsResult["extra_content"]
};
}

View File

@@ -0,0 +1,169 @@
import { print } from 'graphql';
import { createDirectusConnection } from "@/lib/directus";
import getSettingsQuery from '@/graphql/settings/settings.graphql';
export async function getSettings(): Promise<GlobalSettings> {
const client = await createDirectusConnection();
const result = await client.query(print(getSettingsQuery));
const websiteResults = result["Website_Settings"];
const websiteSettings: WebsiteSettings = {
domainName: websiteResults["domain_name"],
titleTemplate: websiteResults["title_template"],
applicationName: websiteResults["application_name"],
colors: {
primary: websiteResults["primary_color"],
secondary: websiteResults["secondary_color"]
},
author: {
name: websiteResults["author_name"],
url: websiteResults["author_url"]
},
owner: websiteResults["owner"],
designer: websiteResults["designer"],
developer: websiteResults["developer"],
copyright: websiteResults["copyright"],
twitter: {
id: websiteResults["twitter_id"],
handle: websiteResults["twitter_handle"]
},
lastModified: websiteResults["date_updated"] !== null ?
new Date(websiteResults["date_updated"]) :
new Date(websiteResults["date_created"])
};
const blogResults = result["Blog_Settings"];
const blogSettings: BlogSettings = {
enabled: blogResults["enabled"],
title: blogResults["title"],
subtext: blogResults["subtext"],
indexRouteTemplate: blogResults["index_route_template"],
blogRouteTemplate: blogResults["blog_route_template"],
lastModified: blogResults["date_updated"] !== null ?
new Date(blogResults["date_updated"]) :
new Date(blogResults["date_created"])
};
const projectResults = result["Project_Settings"];
const projectSettings: ProjectSettings = {
enabled: projectResults["enabled"],
title: projectResults["title"],
subtext: projectResults["subtext"],
indexRouteTemplate: projectResults["index_route_template"],
projectRouteTemplate: projectResults["project_route_template"],
lastModified: projectResults["date_updated"] !== null ?
new Date(projectResults["date_updated"]) :
new Date(projectResults["date_created"])
};
const photoResults = result["Photo_Settings"];
let photoResultsLastModifiedTimestamps: string[] = [
photoResults["date_created"],
photoResults["date_updated"],
photoResults["category_icons"]["date_created"],
photoResults["category_icons"]["date_updated"],
photoResults["category_icons"]["photos_icon"]["created_on"],
photoResults["category_icons"]["location_icon"]["created_on"],
photoResults["category_icons"]["date_icon"]["created_on"],
photoResults["photo_icons"]["date_created"],
photoResults["photo_icons"]["date_updated"],
photoResults["photo_icons"]["previous_icon"]["created_on"],
photoResults["photo_icons"]["next_icon"]["created_on"],
photoResults["photo_icons"]["close_icon"]["created_on"],
photoResults["photo_icons"]["download_icon"]["created_on"]
];
const photoResultsLastModified = photoResultsLastModifiedTimestamps.sort((a: string, b: string) => {
return new Date(b).getTime() - new Date(a).getTime();
});
const photoSettings: WebsitePhotoSettings = {
enabled: photoResults["enabled"],
categoryIndex: {
indexRouteTemplate: photoResults["categories_index_route_template_url"]
},
category: {
routeTemplate: photoResults["category_route_template_url"],
perPage: photoResults["albums_per_category_page"],
icons: {
photos: {
url: photoResults["category_icons"]["photos_icon"]["filename_download"],
height: photoResults["category_icons"]["photos_icon"]["height"],
width: photoResults["category_icons"]["photos_icon"]["width"]
},
location: {
url: photoResults["category_icons"]["location_icon"]["filename_download"],
height: photoResults["category_icons"]["location_icon"]["height"],
width: photoResults["category_icons"]["location_icon"]["width"]
},
date: {
url: photoResults["category_icons"]["date_icon"]["filename_download"],
height: photoResults["category_icons"]["date_icon"]["height"],
width: photoResults["category_icons"]["date_icon"]["width"]
}
}
},
album: {
routeTemplate: photoResults["album_route_template_url"],
perPage: photoResults["photos_per_album_page"]
},
photo: {
routeTemplate: photoResults["photo_route_template_url"],
icons: {
previous: {
url: photoResults["photo_icons"]["previous_icon"]["filename_download"],
height: photoResults["photo_icons"]["previous_icon"]["height"],
width: photoResults["photo_icons"]["previous_icon"]["width"]
},
next: {
url: photoResults["photo_icons"]["next_icon"]["filename_download"],
height: photoResults["photo_icons"]["next_icon"]["height"],
width: photoResults["photo_icons"]["next_icon"]["width"]
},
close: {
url: photoResults["photo_icons"]["close_icon"]["filename_download"],
height: photoResults["photo_icons"]["close_icon"]["height"],
width: photoResults["photo_icons"]["close_icon"]["width"]
},
download: {
url: photoResults["photo_icons"]["download_icon"]["filename_download"],
height: photoResults["photo_icons"]["download_icon"]["height"],
width: photoResults["photo_icons"]["download_icon"]["width"]
}
}
},
lastModified: new Date(photoResultsLastModified[0])
};
const sitemapResults = result["Sitemap_Settings"];
const sitemapSettings: SitemapSettings = {
perPage: sitemapResults["per_page"],
lastModified: sitemapResults["date_updated"] !== null ?
new Date(sitemapResults["date_updated"]) :
new Date(sitemapResults["date_created"])
};
const pluginResults = result["Plugin_Settings"];
const pluginSettings: PluginSettings = {
swetrix: {
id: pluginResults["swetrix_id"],
url: pluginResults["swetrix_url"]
},
lastModified: pluginResults["date_updated"] !== null ?
new Date(pluginResults["date_updated"]) :
new Date(pluginResults["date_created"])
}
if (pluginResults["swetrix_id"] === null && pluginResults["swetrix_url"] === null) {
pluginSettings.swetrix = null;
}
return {
website: websiteSettings,
blog: blogSettings,
project: projectSettings,
photo: photoSettings,
sitemap: sitemapSettings,
plugins: pluginSettings
}
}

8
astro/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
interface ImportMetaEnv {
readonly DIRECTUS_TOKEN: string;
readonly DIRECTUS_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

5
astro/src/graphql.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
declare module '*.graphql' {
import { DocumentNode } from 'graphql';
const Schema: DocumentNode;
export default Schema;
}

View File

@@ -0,0 +1,39 @@
query getAllBlogs($date: String!) {
Blogs(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _gte: $date } }) {
id,
date_created,
date_updated,
status,
title,
url,
date,
content,
tags {
Tags_id {
id,
date_created,
date_updated,
text,
code,
color
}
},
search_engine {
id,
date_created,
date_updated,
title,
description,
thumbnail {
id,
created_on,
filename_disk,
width,
height
},
canonical,
allow_crawler,
priority
}
}
}

View File

@@ -0,0 +1,216 @@
query getAllPages($date: String!) {
Pages(filter: { status: { _eq: "published" } }) {
id,
date_created,
date_updated,
status,
url,
search_engine {
id,
date_created,
date_updated,
title,
description,
thumbnail {
id,
created_on,
filename_disk,
width,
height
},
canonical,
allow_crawler,
priority
},
components {
id,
__typename,
item {
__typename,
...on Hero {
hero_id: id,
hero_created: date_created,
hero_updated: date_updated,
hero_title: title,
hero_text: subtext,
background_image {
id,
created_on,
filename_disk,
width,
height
}
hero_image: background_image {
id,
created_on,
filename_disk,
width,
height
}
background_image {
id,
created_on,
filename_disk,
width,
height
}
}
...on Text_With_Side_Image {
twsi_id: id,
twsi_created: date_created,
twsi_updated: date_updated,
twsi_title: title,
twsi_text: text,
image {
id,
created_on,
filename_disk,
width,
height
},
twsi_image_side: image_side
}
...on Wall_Of_Text {
wot_id: id,
wot_created: date_created,
wot_updated: date_updated,
wot_title: title,
wot_text: text
}
...on Frequently_Asked_Questions {
faq_id: id,
faq_created: date_created,
faq_updated: date_updated,
faq_title: title,
faq_text: text,
questions {
id,
date_created,
date_updated,
question,
answer
}
}
...on Upcoming_Events {
ue_id: id,
ue_created: date_created,
ue_updated: date_updated,
ue_title: title,
ue_text: text,
events(filter: { start_date: { _gte: $date } }) {
id,
date_created,
date_updated,
title,
description,
location,
map_location,
start_date,
end_date
}
}
...on Equipment_Table {
et_id: id,
et_created: date_created,
et_updated: date_updated,
et_title: title,
et_text: text,
items {
id,
date_created,
date_updated,
title,
text,
icon {
id,
created_on,
filename_disk,
width,
height
}
}
}
...on Review_List {
rl_id: id,
rl_created: date_created,
rl_updated: date_updated,
rl_title: title,
rl_text: text,
reviews(sort: ["date"], filter: { status: { _eq: "published" } }) {
id,
date_created,
date_updated,
name,
review,
date,
stars,
thumbnail {
id,
created_on,
filename_disk,
width,
height
}
}
}
...on Contact {
c_id: id,
c_created: date_created,
c_updated: date_updated,
c_title: title,
c_text: text,
methods {
id,
date_created,
date_updated,
title,
text,
icon {
id,
created_on,
filename_disk,
width,
height
},
color
}
}
...on Last_Blogs {
lb_id: id,
lb_created: date_created,
lb_updated: date_updated,
lb_title: title,
lb_read_more_button_text: read_more_button_text,
lb_amount: amount
}
...on Last_Projects {
lp_id: id,
lp_created: date_created,
lp_updated: date_updated,
lp_title: title,
lp_read_more_button_text: read_more_button_text,
lp_amount: amount
}
...on Last_Galleries {
lg_id: id,
lg_created: date_created,
lg_updated: date_updated,
lg_title: title,
lg_read_more_button_text: read_more_button_text,
lg_amount: amount
}
}
}
}
}

View File

@@ -0,0 +1,51 @@
query getAllAlbums($date: String!) {
Photo_Albums(sort: ["-start_date", "-date_created"], filter: { status: { _eq: "published" }, start_date: { _lte: $date }, category: { Photo_Categories_id: { status: { _eq: "published" } } } }) {
id,
date_created,
date_updated,
title,
description,
url,
thumbnail {
id,
created_on,
filename_download,
width,
height
},
start_date,
end_date,
location,
category {
Photo_Categories_id {
id,
status,
date_created,
date_updated,
title,
url,
thumbnail {
id,
created_on,
filename_download,
width,
height
}
}
},
photos(filter: { status: { _eq: "published" } }) {
id,
date_created,
date_updated,
photo {
id,
created_on,
filename_disk,
width,
height
},
text,
sort
}
}
}

View File

@@ -0,0 +1,39 @@
query getAllProjects($date: String!) {
Projects(sort: ["-date", "-date_created"], filter: { status: { _eq: "published" }, date: { _gte: $date } }) {
id,
date_created,
date_updated,
status,
title,
url,
date,
content,
tags {
Tags_id {
id,
date_created,
date_updated,
text,
code,
color
}
},
search_engine {
id,
date_created,
date_updated,
title,
description,
thumbnail {
id,
created_on,
filename_download,
width,
height
},
canonical,
allow_crawler,
priority
}
}
}

View File

@@ -0,0 +1,9 @@
query Robots {
Robots {
id,
date_created,
date_updated,
crawlers,
extra_content
}
}

View File

@@ -0,0 +1,124 @@
query getAllSettings {
Website_Settings {
id,
date_created,
date_updated,
domain_name,
title_template,
application_name,
primary_color,
secondary_color,
author_name,
author_url,
designer,
developer,
owner,
copyright,
twitter_id,
twitter_handle
},
Blog_Settings {
id,
date_created,
date_updated,
enabled,
title,
subtext,
index_route_template,
blog_route_template
},
Project_Settings {
id,
date_created,
date_updated,
enabled,
title,
subtext,
index_route_template,
project_route_template
},
Photo_Settings {
id,
date_created,
date_updated,
enabled,
categories_index_route_template_url,
category_route_template_url,
albums_per_category_page,
category_icons {
id,
date_created,
date_updated,
photos_icon {
id,
created_on,
filename_download,
width,
height
},
location_icon {
id,
created_on,
filename_download,
width,
height
},
date_icon {
id,
created_on,
filename_download,
width,
height
}
},
album_route_template_url,
photos_per_album_page,
photo_route_template_url,
photo_icons {
id,
date_created,
date_updated,
previous_icon {
id,
created_on,
filename_download,
width,
height
},
next_icon {
id,
created_on,
filename_download,
width,
height
},
close_icon {
id,
created_on,
filename_download,
width,
height
},
download_icon {
id,
created_on,
filename_download,
width,
height
}
}
},
Sitemap_Settings {
id,
date_created,
date_updated,
per_page
},
Plugin_Settings {
id,
date_created,
date_updated,
swetrix_id,
swetrix_url
}
}

View File

@@ -0,0 +1,63 @@
---
import { getSettings } from "@/content/settings/settings";
const settings = await getSettings();
---
<html lang="en">
<head>
<!-- High Priority Metadata -->
<meta charset="utf-8" />
<meta name="lanuage" content="EN" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="theme-color" content={settings.website.colors.primary} />
<!-- High Priority Page Metadata -->
<title>{settings.website.titleTemplate.replaceAll("%T", "")}</title>
<!-- Medium Priority Metadata -->
<meta name="msapplication-TileColor" content={settings.website.colors.primary} />
<meta name="msapplication-TileImage" content="" />
<link rel="sitemap" href="/sitemap/index.xml" />
<link rel="alternate" type="application/rss+xml" href="/rss.xml" title="RSS" />
<link rel="canonical" href={`${settings.website.domainName}/`}>
<meta name="robots" content="index,follow" />
<!-- Low Priority Page Metadata -->
<meta name="description" content="" />
<meta property="og:type" content="website" />
<meta property="og:locale" content="en-GB" />
<meta property="og:title" content={settings.website.titleTemplate.replaceAll("%T", "")} />
<meta property="og:description" content="" />
<meta property="og:image:url" content="" />
<meta property="og:image:alt" content="" />
<meta property="og:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
<meta property="og:site_name" content={settings.website.applicationName} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={settings.website.titleTemplate.replaceAll("%T", "")} />
<meta name="twitter:description" content="" />
<meta name="twitter:image" content="" />
<meta name="twitter:image:alt" content="" />
<meta name="twitter:url" content={`${settings.website.domainName}${Astro.url.pathname}`} />
<meta name="twitter:site" content={settings.website.twitter.handle} />
<meta name="twitter:creator" content={settings.website.twitter.handle} />
<meta name="pagename" content="" />
<meta name="category" content="webpage" />
<!-- Low Priority Metadata -->
<meta name="copyright" content={settings.website.copyright} />
<meta name="author" content={`${settings.website.author.name}, ${settings.website.author.url}`} />
<meta name="designer" content={settings.website.designer} />
<meta name="owner" content={settings.website.owner} />
<meta name="developer" content={settings.website.developer} />
<meta name="application-name" content={settings.website.applicationName} />
</head>
<body>
<slot />
</body>
</html>

6
astro/src/lib/dates.ts Normal file
View File

@@ -0,0 +1,6 @@
export function formatDate(date: Date, format: string) {
return format
.replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll("%D", date.getDate().toString().padStart(2, '0'));
}

View File

@@ -0,0 +1,9 @@
import { createDirectus, graphql, staticToken } from "@directus/sdk";
export async function createDirectusConnection() {
const directus = await createDirectus(import.meta.env.DIRECTUS_URL)
.with(graphql())
.with(staticToken(import.meta.env.DIRECTUS_TOKEN));
return directus;
}

12
astro/src/lib/hash.ts Normal file
View File

@@ -0,0 +1,12 @@
import md5 from "md5";
export function getPhotoHash(photo: PhotoAlbumPhoto) {
const hash = md5(JSON.stringify({
id: photo.id,
url: photo.photo.url,
width: photo.photo.width,
height: photo.photo.height
}));
return hash.substring(hash.length - 10);
}

90
astro/src/lib/routing.ts Normal file
View File

@@ -0,0 +1,90 @@
import { getAllBlogs } from "@/content/blogs/blogs";
import { getAllWebpages } from "@/content/pages/pages";
import { getAllAlbums } from "@/content/photos/albums";
import { getAllProjects } from "@/content/projects/projects";
import { getPhotoHash } from "./hash";
export async function getAllRoutesList(settings: GlobalSettings): Promise<string[]> {
let routes: string[] = [];
const webpages = await getAllWebpages();
webpages.forEach((webpage) => {
routes.push(webpage.url);
});
if (settings.blog.enabled) {
const blogs = await getAllBlogs(settings);
blogs.forEach((blog) => {
routes.push(getBlogRoute(settings.blog, blog));
});
}
if (settings.project.enabled) {
const projects = await getAllProjects(settings);
projects.forEach((project) => {
routes.push(getProjectRoute(settings.project, project));
});
}
if (settings.photo.enabled) {
const galleries = await getAllAlbums(settings);
galleries.forEach((gallery) => {
routes.push(getAlbumRoute(settings.photo, gallery));
gallery.photos.forEach((photo) => {
routes.push(getPhotoRoute(settings.photo, gallery, photo));
});
});
}
return routes;
}
export function getBlogRoute(blogSettings: BlogSettings, blog: BlogPost) {
const date = new Date(blog.date);
return blogSettings.blogRouteTemplate
.replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
.replaceAll("%R", blog.url)
.replace(/\/+/g, '/');
}
export function getProjectRoute(projectSettings: ProjectSettings, project: ProjectPost) {
const date = new Date(project.date);
return projectSettings.projectRouteTemplate
.replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
.replaceAll("%R", project.url)
.replace(/\/+/g, '/');
}
export function getAlbumRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum) {
const date = new Date(album.startDate);
return photoSettings.album.routeTemplate
.replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
.replaceAll("%C", album.category.url)
.replaceAll("%R", album.url)
.replace(/\/+/g, '/');
}
export function getPhotoRoute(photoSettings: WebsitePhotoSettings, album: PhotoAlbum, photo: PhotoAlbumPhoto) {
const date = new Date(album.startDate);
return photoSettings.photo.routeTemplate
.replaceAll("%Y", date.getFullYear().toString())
.replaceAll("%M", (date.getMonth() + 1).toString().padStart(2, '0'))
.replaceAll("%D", date.getDate().toString().padStart(2, '0'))
.replaceAll("%C", album.category.url)
.replaceAll("%R", album.url)
.replaceAll("%H", getPhotoHash(photo))
.replace(/\/+/g, '/');
}

View File

@@ -1,17 +1,14 @@
---
import { getAllWebpages } from "@/content/pages/pages";
import { getAllRoutesList } from "@/lib/routing";
import { getSettings } from "@/content/settings/settings"
import WebpageLayout from "@/layouts/WebpageLayout.astro";
const settings = await getSettings();
const routes = await getAllRoutesList(settings);
console.log(routes);
---
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>Astro</title>
</head>
<body>
<h1>Astro</h1>
</body>
</html>
<WebpageLayout>
<h1>Test</h1>
</WebpageLayout>

View File

@@ -0,0 +1,66 @@
import { getRobotsSettings } from "@/content/settings/robots";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
export const GET = (async () => {
const settings = await getSettings();
const robots = await getRobotsSettings();
let crawlers = [
{ id: 'google', name: 'Googlebot' },
{ id: 'bing', name: "Bingbot" },
{ id: "slurp", name: "Slurp" },
{ id: "duckduckgo", name: "DuckDuckBot" },
{ id: "baidu", name: "Baiduspider" },
{ id: "yandex", name: "YandexBot" },
{ id: "sogou", name: "Sogou web spider" },
{ id: "seznam", name: "SeznamBot" },
{ id: "qwantbot", name: "Qwantbot" },
{ id: "naverbot", name: "Naverbot" },
{ id: "coccocbot", name: "Coccocbot" },
{ id: "mojeekbot", name: "Mojeekbot" },
{ id: "ahrefs", name: "Ahrefsbot" },
{ id: "semrush", name: "SemrushBot" },
{ id: "mj12bot", name: "MJ12Bot" },
{ id: "dotbot", name: "DotBot" },
{ id: "petalbot", name: "PetalBot" },
{ id: "gptbot", name: "GPTBot" },
{ id: "ccbot", name: "CCBot" },
{ id: "ia_archiver", name: "ia_archiver" },
{ id: "claudebot", name: "ClaudeBot" },
{ id: "perplexity", name: "PerplexityBot" },
{ id: "facebookexternalhit", name: "facebookexternalhit/1.1" },
{ id: "twitterbot", name: "Twitterbot" },
{ id: "linkedinbot", name: "LinkedInBot" },
{ id: "bytespider", name: "ByteSpider" },
{ id: "applebot", name: "AppleBot" },
{ id: "amazonbot", name: "AmazonBot" }
]
let crawlerContent = "";
crawlers.forEach((crawler) => {
if (robots.crawlers.some(c => c === crawler.id)) {
const crawlerData = crawlers.find(c => c.id === crawler.id);
crawlerContent = crawlerContent +
`User-agent: ${crawlerData!.name}\nAllow: /\nCrawl-delay: 5\nSitemap: ${settings.website.domainName}/sitemap/index.xml`
}
else {
const crawlerData = crawlers.find(c => c.id === crawler.id);
crawlerContent = crawlerContent +
`User-agent: ${crawlerData!.name}\nDisallow: /`
}
crawlerContent = crawlerContent + "\n\n\n"
});
return new Response(crawlerContent.trim(), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "text/plain"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,27 @@
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
let sitemapContent = `
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>${settings.website.applicationName}</title>
<description>This is the RSS feed of ${settings.website.applicationName}</description>
<link>${settings.website.domainName}</link>
<lastBuildDate>Sat, 13 Dec 2003 18:30:02 GMT</lastBuildDate>
</channel>
</rss>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,70 @@
import { getAllAlbums } from "@/content/photos/albums";
import { getSettings } from "@/content/settings/settings";
import { getAlbumRoute } from "@/lib/routing";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async ({ params }) => {
const settings = await getSettings();
if (!settings.photo.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const currentPage = params.page;
const albums = await getAllAlbums(settings);
const selectedAlbums = albums.slice(
((Number(currentPage) - 1) * settings.sitemap.perPage),
Number(currentPage) * settings.sitemap.perPage - 1
);
let pages: SitemapPage[] = [];
selectedAlbums.forEach((album) => {
pages.push({
url: getAlbumRoute(settings.photo, album),
lastModified: album.lastModified
});
});
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map((page) => `
<sitemap>
<loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;
export async function getStaticPaths() {
const settings = await getSettings();
const albums = await getAllAlbums(settings);
const albumCount = albums.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(albumCount / perPage);
let items: any[] = [];
for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } });
}
return items;
}

View File

@@ -0,0 +1,68 @@
import { getAllAlbums } from "@/content/photos/albums";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
if (!settings.photo.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const albums = await getAllAlbums(settings);
const albumCount = albums.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(albumCount / perPage);
let sitemaps: SitemapIndex[] = [];
for (let i = 0; i < pages; i++) {
const selectedProjects = albums.slice(
((Number(i + 1) - 1) * settings.sitemap.perPage),
Number(i + 1) * settings.sitemap.perPage - 1
);
let dates = [
settings.sitemap.lastModified,
settings.photo.lastModified,
settings.website.lastModified
];
selectedProjects.forEach((project) => {
dates.push(project.lastModified);
});
const lastModified = dates.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemaps.push({
url: `/sitemap/albums-${i + 1}.xml`,
lastModified: lastModified[0]
});
}
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemaps.map((item) => `
<sitemap>
<loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,70 @@
import { getAllBlogs } from "@/content/blogs/blogs";
import { getSettings } from "@/content/settings/settings";
import { getBlogRoute } from "@/lib/routing";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async ({ params }) => {
const settings = await getSettings();
if (!settings.blog.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const currentPage = params.page;
const blogs = await getAllBlogs(settings);
const selectedBlogs = blogs.slice(
((Number(currentPage) - 1) * settings.sitemap.perPage),
Number(currentPage) * settings.sitemap.perPage - 1
);
let pages: SitemapPage[] = [];
selectedBlogs.forEach((blog) => {
pages.push({
url: getBlogRoute(settings.blog, blog),
lastModified: blog.lastModified
});
})
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map((page) => `
<sitemap>
<loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;
export async function getStaticPaths() {
const settings = await getSettings();
const blogs = await getAllBlogs(settings);
const blogCount = blogs.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(blogCount / perPage);
let items: any[] = [];
for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } });
}
return items;
}

View File

@@ -0,0 +1,68 @@
import { getAllBlogs } from "@/content/blogs/blogs";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
if (!settings.blog.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const blogs = await getAllBlogs(settings);
const blogCount = blogs.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(blogCount / perPage);
let sitemaps: SitemapIndex[] = [];
for (let i = 0; i < pages; i++) {
const selectedBlogs = blogs.slice(
((Number(i + 1) - 1) * settings.sitemap.perPage),
Number(i + 1) * settings.sitemap.perPage - 1
);
let dates = [
settings.sitemap.lastModified,
settings.blog.lastModified,
settings.website.lastModified
];
selectedBlogs.forEach((blog) => {
dates.push(blog.lastModified);
});
const lastModified = dates.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemaps.push({
url: `/sitemap/blogs-${i + 1}.xml`,
lastModified: lastModified[0]
});
}
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemaps.map((item) => `
<sitemap>
<loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,104 @@
import { getAllBlogs } from "@/content/blogs/blogs";
import { getAllAlbums } from "@/content/photos/albums";
import { getAllProjects } from "@/content/projects/projects";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
let sitemapIndex: SitemapIndex[] = [
{
url: "/sitemap/pages.xml",
lastModified: new Date()
}
];
if (settings.blog.enabled) {
const blogLastModifieds = [
settings.blog.lastModified,
settings.sitemap.lastModified,
settings.website.lastModified
];
let blogs = await getAllBlogs(settings);
blogs.forEach((blog) => {
blogLastModifieds.push(blog.lastModified);
});
const lastModifiedBlogs = blogLastModifieds.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemapIndex.push({
url: "/sitemap/blogs.xml",
lastModified: lastModifiedBlogs[0]
});
};
if (settings.project.enabled) {
const projectLastModifieds = [
settings.project.lastModified,
settings.sitemap.lastModified,
settings.website.lastModified
];
let projects = await getAllProjects(settings);
projects.forEach((project) => {
projectLastModifieds.push(project.lastModified);
});
const lastModifiedProjects = projectLastModifieds.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemapIndex.push({
url: "/sitemap/projects.xml",
lastModified: lastModifiedProjects[0]
});
};
if (settings.photo.enabled) {
const photoLastModifieds = [
settings.photo.lastModified,
settings.sitemap.lastModified,
settings.website.lastModified
];
let albums = await getAllAlbums(settings);
albums.forEach((album) => {
photoLastModifieds.push(album.lastModified);
});
const lastModifiedAlbums = photoLastModifieds.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemapIndex.push({
url: "/sitemap/albums.xml",
lastModified: lastModifiedAlbums[0]
});
};
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemapIndex.map((item) => `
<sitemap>
<loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,62 @@
import { getAllWebpages } from "@/content/pages/pages";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async ({ params }) => {
const settings = await getSettings();
const currentPage = params.page;
const webPages = await getAllWebpages();
const selectedPages = webPages.slice(
((Number(currentPage) - 1) * settings.sitemap.perPage),
Number(currentPage) * settings.sitemap.perPage - 1
)
let pages: SitemapPage[] = [];
selectedPages.forEach((page) => {
pages.push({
url: page.url,
lastModified: page.lastModified
});
});
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map((page) => `
<sitemap>
<loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;
export async function getStaticPaths() {
const settings = await getSettings();
const webPages = await getAllWebpages();
const pageCount = webPages.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(pageCount / perPage);
let items: any[] = [];
for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } });
}
return items;
}

View File

@@ -0,0 +1,42 @@
import { getAllWebpages } from "@/content/pages/pages";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
const webPages = await getAllWebpages();
const pageCount = webPages.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(pageCount / perPage);
let sitemaps: SitemapIndex[] = [];
for (let i = 0; i < pages; i++) {
sitemaps.push({
url: `/sitemap/pages-${i + 1}.xml`,
lastModified: new Date()
});
}
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemaps.map((item) => `
<sitemap>
<loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

View File

@@ -0,0 +1,70 @@
import { getAllProjects } from "@/content/projects/projects";
import { getSettings } from "@/content/settings/settings";
import { getProjectRoute } from "@/lib/routing";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async ({ params }) => {
const settings = await getSettings();
if (!settings.project.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const currentPage = params.page;
const projects = await getAllProjects(settings);
const selectedProjects = projects.slice(
((Number(currentPage) - 1) * settings.sitemap.perPage),
Number(currentPage) * settings.sitemap.perPage - 1
);
let pages: SitemapPage[] = [];
selectedProjects.forEach((project) => {
pages.push({
url: getProjectRoute(settings.project, project),
lastModified: project.lastModified
});
});
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${pages.map((page) => `
<sitemap>
<loc>${settings.website.domainName}${page.url}</loc>
<lastmod>${page.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;
export async function getStaticPaths() {
const settings = await getSettings();
const projects = await getAllProjects(settings);
const projectCount = projects.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(projectCount / perPage);
let items: any[] = [];
for (let i = 0; i < pages; i++) {
items.push({ params: { page: (i + 1) } });
}
return items;
}

View File

@@ -0,0 +1,68 @@
import { getAllProjects } from "@/content/projects/projects";
import { getSettings } from "@/content/settings/settings";
import type { APIRoute } from "astro";
import minifyXML from "minify-xml";
export const GET = (async () => {
const settings = await getSettings();
if (!settings.blog.enabled) {
return new Response(null, {
status: 204,
statusText: "Not Found"
});
}
const projects = await getAllProjects(settings);
const projectCount = projects.length;
const perPage = settings.sitemap.perPage;
const pages = Math.ceil(projectCount / perPage);
let sitemaps: SitemapIndex[] = [];
for (let i = 0; i < pages; i++) {
const selectedProjects = projects.slice(
((Number(i + 1) - 1) * settings.sitemap.perPage),
Number(i + 1) * settings.sitemap.perPage - 1
);
let dates = [
settings.sitemap.lastModified,
settings.project.lastModified,
settings.website.lastModified
];
selectedProjects.forEach((project) => {
dates.push(project.lastModified);
});
const lastModified = dates.sort((a: Date, b: Date) => {
return b.getTime() - a.getTime();
});
sitemaps.push({
url: `/sitemap/projects-${i + 1}.xml`,
lastModified: lastModified[0]
});
}
let sitemapContent = `
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${sitemaps.map((item) => `
<sitemap>
<loc>${settings.website.domainName}${item.url}</loc>
<lastmod>${item.lastModified.toISOString()}</lastmod>
</sitemap>
`).join('')}
</sitemapindex>
`;
return new Response(minifyXML(sitemapContent), {
status: 200,
statusText: "OK",
headers: {
"Content-Type": "application/xml"
}
});
}) satisfies APIRoute;

12
astro/src/types/blogs/blog.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
type BlogPost = {
title: string;
url: string;
date: string;
content: string;
tags: Tag[];
searchEngine: SearchEngine;
lastModified: Date;
}

5
astro/src/types/common/images.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
type PhotoProps = {
url: string;
width: number;
height: number;
}

View File

@@ -0,0 +1,8 @@
type SearchEngine = {
title: string;
description: string;
thumbnail: PhotoProps;
canonical: string | null;
allowCrawlers: boolean;
priority: number;
}

5
astro/src/types/common/tag.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
type Tag = {
text: string;
code: string;
color: string;
}

16
astro/src/types/components/contact.d.ts vendored Normal file
View File

@@ -0,0 +1,16 @@
type ContactComponent = {
component: "Contact";
id: string;
title: string;
text: string;
methods: ContactMethod[];
}
type ContactMethod = {
id: string;
title: string;
text: string;
color: string;
icon: PhotoProps;
}

View File

@@ -0,0 +1,15 @@
type EquipmentTableComponent = {
component: "EquipmentTable";
id: string;
title: string;
text: string;
items: EquipmentTableItem[];
}
type EquipmentTableItem = {
id: string;
title: string;
text: string;
icon: PhotoProps;
}

18
astro/src/types/components/events.d.ts vendored Normal file
View File

@@ -0,0 +1,18 @@
type UpcomingEventsComponent = {
component: "UpcomingEvents";
id: string;
title: string;
text: string;
events: UpcomingEvent[];
}
type UpcomingEvent = {
id: string;
title: string;
description: string;
location: string;
mapLocation: [number, number];
startDate: Date;
endDate: Date;
}

14
astro/src/types/components/faq.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
type FrequentlyAskedQuestionsComponent = {
component: "FrequentlyAskedQuestions";
id: string;
title: string;
text: string;
questions: FrequentlyAskedQuestion[];
}
type FrequentlyAskedQuestion = {
id: string;
question: string;
answer: string;
}

8
astro/src/types/components/hero.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
type HeroComponent = {
component: "Hero";
id: string;
title: string;
text: string;
backgroundImage: PhotoProps;
}

View File

@@ -0,0 +1,26 @@
type LastBlogsComponent = {
component: "LastBlogs";
id: string;
title: string;
readMoreButtonText: string;
amount: number;
}
type LastProjectsComponent = {
component: "LastProjects";
id: string;
title: string;
readMoreButtonText: string;
amount: number;
}
type LastGalleriesComponent = {
component: "LastGalleries";
id: string;
title: string;
readMoreButtonText: string;
amount: number;
}

17
astro/src/types/components/reviews.d.ts vendored Normal file
View File

@@ -0,0 +1,17 @@
type ReviewListComponent = {
component: "Reviews";
id: string;
title: string;
text: string;
reviews: Review[];
}
type Review = {
id: string;
name: string;
review: string;
date: string;
stars: number;
thumbnail: PhotoProps;
}

View File

@@ -0,0 +1,9 @@
type TextWithImageComponent = {
component: "TextWithImage";
id: string;
title: string;
text: string;
image: PhotoProps;
imageSide: "left" | "right";
}

View File

@@ -0,0 +1,7 @@
type WallOfTextComponent = {
component: "WallOfText";
id: string;
title: string;
text: string;
}

21
astro/src/types/pages/page.d.ts vendored Normal file
View File

@@ -0,0 +1,21 @@
type WebPage = {
id: string;
lastModified: Date;
url: string;
searchEngine: SearchEngine;
components: WebpageComponent[];
}
type WebpageComponent =
ContactComponent |
EquipmentTableComponent |
UpcomingEventsComponent |
FrequentlyAskedQuestionsComponent |
HeroComponent |
LastBlogsComponent |
LastProjectsComponent |
LastGalleriesComponent |
ReviewListComponent |
TextWithImageComponent |
WallOfTextComponent;

28
astro/src/types/photos/album.d.ts vendored Normal file
View File

@@ -0,0 +1,28 @@
type PhotoAlbum = {
title: string;
url: string;
description: string | null;
thumbnail: PhotoProps;
startDate: string;
endDate: string | null;
location: string | null;
category: PhotoAlbumCategory;
photos: PhotoAlbumPhoto[];
lastModified: Date;
}
type PhotoAlbumCategory = {
title: string;
url: string;
thumbnail: PhotoProps;
}
type PhotoAlbumPhoto = {
id: string;
photo: PhotoProps;
text: string | null;
}

3
astro/src/types/photos/category.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
type PhotoCategory = {
}

3
astro/src/types/photos/photo.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
type PhotoPhoto = {
}

12
astro/src/types/projects/project.d.ts vendored Normal file
View File

@@ -0,0 +1,12 @@
type ProjectPost = {
title: string;
url: string;
date: string;
content: string;
tags: Tag[];
searchEngine: SearchEngine;
lastModified: Date;
}

11
astro/src/types/settings/blog.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
type BlogSettings = {
enabled: string;
title: string;
subtext: string | null;
indexRouteTemplate: string;
blogRouteTemplate: string;
lastModified: Date;
}

39
astro/src/types/settings/photo.d.ts vendored Normal file
View File

@@ -0,0 +1,39 @@
type WebsitePhotoSettings = {
enabled: boolean;
categoryIndex: WebsitePhotoSettingsCategoryIndex;
category: WebsitePhotoSettingsCategory;
album: WebsitePhotoSettingsAlbum;
photo: WebsitePhotoSettingsPhoto;
lastModified: Date;
}
type WebsitePhotoSettingsCategoryIndex = {
indexRouteTemplate: string;
}
type WebsitePhotoSettingsCategory = {
routeTemplate: string;
perPage: number;
icons: {
photos: PhotoProps;
location: PhotoProps;
date: PhotoProps;
}
}
type WebsitePhotoSettingsAlbum = {
routeTemplate: string;
perPage: number;
}
type WebsitePhotoSettingsPhoto = {
routeTemplate: string;
icons: {
previous: PhotoProps;
next: PhotoProps;
close: PhotoProps;
download: PhotoProps;
}
}

10
astro/src/types/settings/plugin.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
type PluginSettings = {
swetrix: PluginSettingsSwetrix | null;
lastModified: Date;
}
type PluginSettingsSwetrix = {
id: string | null;
url: string | null;
}

11
astro/src/types/settings/project.d.ts vendored Normal file
View File

@@ -0,0 +1,11 @@
type ProjectSettings = {
enabled: string;
title: string;
subtext: string | null;
indexRouteTemplate: string;
projectRouteTemplate: string;
lastModified: Date;
}

4
astro/src/types/settings/robots.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
type RobotsSettings = {
crawlers: string[];
extraContent: string | null;
}

8
astro/src/types/settings/setting.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
type GlobalSettings = {
website: WebsiteSettings;
blog: BlogSettings;
project: ProjectSettings;
photo: WebsitePhotoSettings;
sitemap: SitemapSettings;
plugins: PluginSettings;
}

5
astro/src/types/settings/sitemap.d.ts vendored Normal file
View File

@@ -0,0 +1,5 @@
type SitemapSettings = {
perPage: number;
lastModified: Date;
}

34
astro/src/types/settings/website.d.ts vendored Normal file
View File

@@ -0,0 +1,34 @@
type WebsiteSettings = {
domainName: string;
titleTemplate: string;
applicationName: string;
colors: WebsiteSettingsColors;
author: WebsiteSettingsAuthor;
owner: string;
designer: string;
developer: string;
copyright: string;
twitter: WebsiteSettingsTwitter;
lastModified: Date;
}
type WebsiteSettingsColors = {
primary: string;
secondary: string | null;
}
type WebsiteSettingsAuthor = {
name: string;
url: string;
}
type WebsiteSettingsTwitter = {
id: string;
handle: string;
}

9
astro/src/types/sitemaps/sitemap.d.ts vendored Normal file
View File

@@ -0,0 +1,9 @@
type SitemapIndex = {
url: string;
lastModified: Date;
}
type SitemapPage = {
url: string;
lastModified: Date;
}

View File

@@ -9,6 +9,9 @@
],
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "preact"
"jsxImportSource": "preact",
"paths": {
"@/*": ["./src/*"]
}
}
}