mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 10:51:36 +01:00
Use SvelteKit render function for templating and add error page
This commit is contained in:
@@ -100,7 +100,6 @@ in
|
|||||||
User = cfg.user;
|
User = cfg.user;
|
||||||
Group = cfg.group;
|
Group = cfg.group;
|
||||||
Restart = "always";
|
Restart = "always";
|
||||||
WorkingDirectory = "${cfg.package}/web-app";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
script = ''
|
script = ''
|
||||||
|
|||||||
@@ -32,23 +32,12 @@ let
|
|||||||
cp -r db/migrations/* $out/rest-api/db/migrations
|
cp -r db/migrations/* $out/rest-api/db/migrations
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
templates = stdenv.mkDerivation {
|
|
||||||
inherit pname version;
|
|
||||||
name = "archtika-templates";
|
|
||||||
src = ../templates;
|
|
||||||
installPhase = ''
|
|
||||||
mkdir -p $out/templates
|
|
||||||
cp -r * $out/templates
|
|
||||||
'';
|
|
||||||
};
|
|
||||||
in
|
in
|
||||||
symlinkJoin {
|
symlinkJoin {
|
||||||
name = pname;
|
name = pname;
|
||||||
paths = [
|
paths = [
|
||||||
web
|
web
|
||||||
api
|
api
|
||||||
templates
|
|
||||||
];
|
];
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="../styles.css">
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<nav>
|
|
||||||
{{logo}}
|
|
||||||
</nav>
|
|
||||||
<header>
|
|
||||||
{{cover_image}}
|
|
||||||
{{title}}
|
|
||||||
{{publication_date}}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
{{main_content}}
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{{additional_text}}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link rel="stylesheet" href="styles.css">
|
|
||||||
<title>Document</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<nav>
|
|
||||||
{{logo}}
|
|
||||||
</nav>
|
|
||||||
<header>
|
|
||||||
{{title}}
|
|
||||||
</header>
|
|
||||||
<main>
|
|
||||||
<section>
|
|
||||||
{{main_content}}
|
|
||||||
</section>
|
|
||||||
<section>
|
|
||||||
{{articles}}
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
<footer>
|
|
||||||
{{additional_text}}
|
|
||||||
</footer>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
:root {
|
|
||||||
color-scheme: light dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin-inline: auto;
|
|
||||||
inline-size: min(100% - 2rem, 75ch);
|
|
||||||
line-height: 1.5;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
img,
|
|
||||||
picture,
|
|
||||||
svg,
|
|
||||||
video {
|
|
||||||
max-inline-size: 100%;
|
|
||||||
block-size: auto;
|
|
||||||
}
|
|
||||||
42
web-app/src/lib/templates/blog/BlogArticle.svelte
Normal file
42
web-app/src/lib/templates/blog/BlogArticle.svelte
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { title, logoType, logo, mainContent, coverImage, publicationDate, footerAdditionalText } =
|
||||||
|
$props<{
|
||||||
|
title: string;
|
||||||
|
logoType: "text" | "image";
|
||||||
|
logo: string;
|
||||||
|
mainContent: string;
|
||||||
|
coverImage: string;
|
||||||
|
publicationDate: string;
|
||||||
|
footerAdditionalText: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<head>
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
{#if logoType === "text"}
|
||||||
|
<p>
|
||||||
|
<strong>{logo}</strong>
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<img src={logo} alt="" />
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<img src={coverImage} alt="" />
|
||||||
|
<h1>{title}</h1>
|
||||||
|
<p>{publicationDate}</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
{@html mainContent}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
{footerAdditionalText}
|
||||||
|
</footer>
|
||||||
57
web-app/src/lib/templates/blog/BlogIndex.svelte
Normal file
57
web-app/src/lib/templates/blog/BlogIndex.svelte
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
const { title, logoType, logo, mainContent, articles, footerAdditionalText } = $props<{
|
||||||
|
title: string;
|
||||||
|
logoType: "text" | "image";
|
||||||
|
logo: string;
|
||||||
|
mainContent: string;
|
||||||
|
articles: any[];
|
||||||
|
footerAdditionalText: string;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:head>
|
||||||
|
<head>
|
||||||
|
<title>{title}</title>
|
||||||
|
</head>
|
||||||
|
</svelte:head>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
{#if logoType === "text"}
|
||||||
|
<p>
|
||||||
|
<strong>{logo}</strong>
|
||||||
|
</p>
|
||||||
|
{:else}
|
||||||
|
<img src={logo} alt="" />
|
||||||
|
{/if}
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1>{title}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section>
|
||||||
|
{@html mainContent}
|
||||||
|
</section>
|
||||||
|
{#if articles.length > 0}
|
||||||
|
<section>
|
||||||
|
<h2>Articles</h2>
|
||||||
|
|
||||||
|
{#each articles as article}
|
||||||
|
{@const articleFileName = article.title.toLowerCase().split(" ").join("-")}
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<p>{article.publication_date}</p>
|
||||||
|
<h3>
|
||||||
|
<a href="./articles/{articleFileName}.html">{article.title}</a>
|
||||||
|
</h3>
|
||||||
|
<p>{article.meta_description ?? "No description provided"}</p>
|
||||||
|
</article>
|
||||||
|
{/each}
|
||||||
|
</section>
|
||||||
|
{/if}
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
{footerAdditionalText}
|
||||||
|
</footer>
|
||||||
7
web-app/src/routes/(authenticated)/+error.svelte
Normal file
7
web-app/src/routes/(authenticated)/+error.svelte
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<h1>Not found or access denied</h1>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h1 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { LayoutServerLoad } from "./$types";
|
import type { LayoutServerLoad } from "./$types";
|
||||||
import { API_BASE_PREFIX } from "$lib/utils";
|
import { API_BASE_PREFIX } from "$lib/utils";
|
||||||
|
import { error } from "@sveltejs/kit";
|
||||||
|
|
||||||
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
||||||
const websiteData = await fetch(`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`, {
|
const websiteData = await fetch(`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`, {
|
||||||
@@ -11,6 +12,10 @@ export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!websiteData.ok) {
|
||||||
|
throw error(404, "Website not found");
|
||||||
|
}
|
||||||
|
|
||||||
const homeData = await fetch(`${API_BASE_PREFIX}/home?website_id=eq.${params.websiteId}`, {
|
const homeData = await fetch(`${API_BASE_PREFIX}/home?website_id=eq.${params.websiteId}`, {
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import { readFile, mkdir, writeFile } from "node:fs/promises";
|
import { mkdir, writeFile } from "node:fs/promises";
|
||||||
import { join } from "node:path";
|
import { join } from "node:path";
|
||||||
import { md } from "$lib/utils";
|
import { md } from "$lib/utils";
|
||||||
import type { Actions, PageServerLoad } from "./$types";
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
import { API_BASE_PREFIX, NGINX_BASE_PREFIX } from "$lib/utils";
|
import { API_BASE_PREFIX, NGINX_BASE_PREFIX } from "$lib/utils";
|
||||||
|
import { render } from "svelte/server";
|
||||||
|
import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
|
||||||
|
import BlogArticle from "$lib/templates/blog/BlogArticle.svelte";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, fetch, cookies, locals }) => {
|
export const load: PageServerLoad = async ({ params, fetch, cookies }) => {
|
||||||
const websiteOverviewData = await fetch(
|
const websiteOverviewData = await fetch(
|
||||||
`${API_BASE_PREFIX}/website_overview?id=eq.${params.websiteId}`,
|
`${API_BASE_PREFIX}/website_overview?id=eq.${params.websiteId}`,
|
||||||
{
|
{
|
||||||
@@ -41,56 +44,18 @@ export const actions: Actions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
|
const generateStaticFiles = async (websiteData: any, isPreview: boolean = true) => {
|
||||||
const templatePath = join(
|
const { head, body } = render(BlogIndex, {
|
||||||
process.cwd(),
|
props: {
|
||||||
"..",
|
title: websiteData.title,
|
||||||
"templates",
|
logoType: websiteData.logo_type,
|
||||||
websiteData.content_type.toLowerCase()
|
logo: websiteData.logo_text,
|
||||||
);
|
mainContent: md.render(websiteData.main_content ?? ""),
|
||||||
|
articles: websiteData.articles ?? [],
|
||||||
const indexFile = await readFile(join(templatePath, "index.html"), { encoding: "utf-8" });
|
footerAdditionalText: websiteData.additional_text ?? ""
|
||||||
const articleFile = await readFile(join(templatePath, "article.html"), {
|
}
|
||||||
encoding: "utf-8"
|
|
||||||
});
|
});
|
||||||
const stylesFile = await readFile(join(templatePath, "styles.css"), { encoding: "utf-8" });
|
|
||||||
|
|
||||||
const indexFileContents = indexFile
|
const indexFileContents = head.concat(body);
|
||||||
.replace(
|
|
||||||
"{{logo}}",
|
|
||||||
websiteData.logo_type === "text"
|
|
||||||
? `<strong>${websiteData.logo_text}</strong>`
|
|
||||||
: `<img src="https://picsum.photos/32/32" />`
|
|
||||||
)
|
|
||||||
.replace("{{title}}", `<h1>${websiteData.title}</h1>`)
|
|
||||||
.replace("{{main_content}}", md.render(websiteData.main_content ?? ""))
|
|
||||||
.replace(
|
|
||||||
"{{articles}}",
|
|
||||||
Array.isArray(websiteData.articles) && websiteData.articles.length > 0
|
|
||||||
? `
|
|
||||||
<h2>Articles</h2>
|
|
||||||
${websiteData.articles
|
|
||||||
.map(
|
|
||||||
(article: { title: string; publication_date: string; meta_description: string }) => {
|
|
||||||
const articleFileName = article.title.toLowerCase().split(" ").join("-");
|
|
||||||
|
|
||||||
return `
|
|
||||||
<article>
|
|
||||||
<p>${article.publication_date}</p>
|
|
||||||
<h3>
|
|
||||||
<a href="./articles/${articleFileName}.html">
|
|
||||||
${article.title}
|
|
||||||
</a>
|
|
||||||
</h3>
|
|
||||||
<p>${article.meta_description ?? "No description provided"}</p>
|
|
||||||
</article>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.join("")}
|
|
||||||
`
|
|
||||||
: "<h2>Articles</h2><p>No articles available at this time.</p>"
|
|
||||||
)
|
|
||||||
.replace("{{additional_text}}", md.render(websiteData.additional_text ?? ""));
|
|
||||||
|
|
||||||
let uploadDir = "";
|
let uploadDir = "";
|
||||||
|
|
||||||
@@ -109,32 +74,26 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
|
|||||||
}
|
}
|
||||||
|
|
||||||
await mkdir(uploadDir, { recursive: true });
|
await mkdir(uploadDir, { recursive: true });
|
||||||
|
|
||||||
await writeFile(join(uploadDir, "index.html"), indexFileContents);
|
await writeFile(join(uploadDir, "index.html"), indexFileContents);
|
||||||
|
|
||||||
await mkdir(join(uploadDir, "articles"), { recursive: true });
|
await mkdir(join(uploadDir, "articles"), { recursive: true });
|
||||||
|
|
||||||
for (const article of websiteData.articles ?? []) {
|
for (const article of websiteData.articles ?? []) {
|
||||||
const articleFileName = article.title.toLowerCase().split(" ").join("-");
|
const articleFileName = article.title.toLowerCase().split(" ").join("-");
|
||||||
|
|
||||||
const articleFileContents = articleFile
|
const { head, body } = render(BlogArticle, {
|
||||||
.replace(
|
props: {
|
||||||
"{{logo}}",
|
title: article.title,
|
||||||
websiteData.logo_type === "text"
|
logoType: websiteData.logo_type,
|
||||||
? `<strong>${websiteData.logo_text}</strong>`
|
logo: websiteData.logo_text,
|
||||||
: `<img src="https://picsum.photos/32/32" />`
|
coverImage: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`,
|
||||||
)
|
publicationDate: article.publication_date,
|
||||||
.replace(
|
mainContent: md.render(article.main_content ?? ""),
|
||||||
"{{cover_image}}",
|
footerAdditionalText: websiteData.additional_text ?? ""
|
||||||
`<img src="${article.cover_image ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}` : "https://picsum.photos/600/200"}" />`
|
}
|
||||||
)
|
});
|
||||||
.replace("{{title}}", `<h1>${article.title}</h1>`)
|
|
||||||
.replace("{{publication_date}}", `<p>${article.publication_date}</p>`)
|
const articleFileContents = head.concat(body);
|
||||||
.replace("{{main_content}}", md.render(article.main_content ?? ""))
|
|
||||||
.replace("{{additional_text}}", md.render(websiteData.additional_text ?? ""));
|
|
||||||
|
|
||||||
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
|
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFile(join(uploadDir, "styles.css"), stylesFile);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
const { data, children } = $props<{ data: LayoutServerData; children: Snippet }>();
|
const { data, children } = $props<{ data: LayoutServerData; children: Snippet }>();
|
||||||
|
|
||||||
const isProjectRoute = $derived($page.url.pathname.startsWith("/website"));
|
const isProjectRoute = $derived($page.url.pathname.startsWith("/website") && !$page.error);
|
||||||
const routeName = $derived(
|
const routeName = $derived(
|
||||||
$page.url.pathname === "/"
|
$page.url.pathname === "/"
|
||||||
? "Dashboard"
|
? "Dashboard"
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{#if !isProjectRoute}
|
{#if !isProjectRoute && !$page.error}
|
||||||
<header>
|
<header>
|
||||||
<h1>{routeName}</h1>
|
<h1>{routeName}</h1>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Reference in New Issue
Block a user