Files
archtika/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts

257 lines
7.7 KiB
TypeScript
Raw Normal View History

import { dev } from "$app/environment";
2024-09-25 21:45:01 +02:00
import { API_BASE_PREFIX, apiRequest } from "$lib/server/utils";
import BlogArticle from "$lib/templates/blog/BlogArticle.svelte";
import BlogIndex from "$lib/templates/blog/BlogIndex.svelte";
2024-08-27 16:39:29 +02:00
import DocsArticle from "$lib/templates/docs/DocsArticle.svelte";
import DocsIndex from "$lib/templates/docs/DocsIndex.svelte";
import { type WebsiteOverview, hexToHSL } from "$lib/utils";
import { mkdir, readFile, writeFile, chmod, readdir } from "node:fs/promises";
import { join } from "node:path";
import { render } from "svelte/server";
import type { Actions, PageServerLoad } from "./$types";
const getOverviewFetchUrl = (websiteId: string) => {
return `${API_BASE_PREFIX}/website?id=eq.${websiteId}&select=*,settings(*),header(*),home(*),footer(*),article(*,docs_category(*)),legal_information(*),domain_prefix(*)`;
};
export const load: PageServerLoad = async ({ params, fetch, parent }) => {
2024-09-25 21:45:01 +02:00
const websiteOverview: WebsiteOverview = (
await apiRequest(fetch, getOverviewFetchUrl(params.websiteId), "GET", {
headers: {
Accept: "application/vnd.pgrst.object+json"
},
returnData: true
})
2024-09-25 21:45:01 +02:00
).data;
2024-10-04 17:09:51 +02:00
const { websitePreviewUrl, websiteProdUrl } = await generateStaticFiles(websiteOverview);
const { permissionLevel } = await parent();
return {
websiteOverview,
2024-08-20 19:17:05 +02:00
websitePreviewUrl,
websiteProdUrl,
permissionLevel
};
};
export const actions: Actions = {
2024-09-25 21:45:01 +02:00
publishWebsite: async ({ fetch, params }) => {
const websiteOverview: WebsiteOverview = (
await apiRequest(fetch, getOverviewFetchUrl(params.websiteId), "GET", {
headers: {
Accept: "application/vnd.pgrst.object+json"
},
returnData: true
})
2024-09-25 21:45:01 +02:00
).data;
const publish = await apiRequest(
2024-09-25 21:45:01 +02:00
fetch,
`${API_BASE_PREFIX}/website?id=eq.${params.websiteId}`,
"PATCH",
{
body: {
is_published: true
},
successMessage: "Successfully published website"
}
);
if (!publish.success) {
return publish;
}
await generateStaticFiles(websiteOverview, false);
return publish;
},
2024-09-25 21:45:01 +02:00
createUpdateCustomDomainPrefix: async ({ request, fetch, params }) => {
const data = await request.formData();
return await apiRequest(fetch, `${API_BASE_PREFIX}/rpc/set_domain_prefix`, "POST", {
2024-09-25 21:45:01 +02:00
body: {
website_id: params.websiteId,
2024-09-25 22:05:39 +02:00
prefix: data.get("domain-prefix")
2024-09-25 21:45:01 +02:00
},
successMessage: "Successfully created/updated domain prefix"
});
},
2024-09-25 21:45:01 +02:00
deleteCustomDomainPrefix: async ({ fetch, params }) => {
return await apiRequest(fetch, `${API_BASE_PREFIX}/rpc/delete_domain_prefix`, "POST", {
body: {
website_id: params.websiteId
},
successMessage: "Successfully deleted domain prefix"
});
}
};
const generateStaticFiles = async (websiteData: WebsiteOverview, isPreview = true) => {
2024-10-04 17:09:51 +02:00
const websitePreviewUrl = `${
dev
? "http://localhost:18000"
: process.env.ORIGIN
? process.env.ORIGIN
: "http://localhost:18000"
}/previews/${websiteData.id}/`;
const websiteProdUrl = dev
? `http://localhost:18000/${websiteData.domain_prefix?.prefix ?? websiteData.id}/`
: process.env.ORIGIN
? process.env.ORIGIN.replace(
"//",
`//${websiteData.domain_prefix?.prefix ?? websiteData.id}.`
)
: `http://localhost:18000/${websiteData.domain_prefix?.prefix ?? websiteData.id}/`;
const fileContents = (head: string, body: string) => {
return `
2024-09-07 16:45:20 +02:00
<!DOCTYPE html>
<html lang="en">
<head>
${head}
</head>
<body>
${body}
</body>
</html>`;
};
const { head, body } = render(websiteData.content_type === "Blog" ? BlogIndex : DocsIndex, {
props: {
websiteOverview: websiteData,
apiUrl: API_BASE_PREFIX,
2024-10-04 17:09:51 +02:00
isLegalPage: false,
websiteUrl: isPreview ? websitePreviewUrl : websiteProdUrl
}
});
let uploadDir = "";
if (isPreview) {
uploadDir = join("/", "var", "www", "archtika-websites", "previews", websiteData.id);
} else {
uploadDir = join(
"/",
"var",
"www",
"archtika-websites",
websiteData.domain_prefix?.prefix ?? websiteData.id
);
}
await mkdir(uploadDir, { recursive: true });
await writeFile(join(uploadDir, "index.html"), fileContents(head, body));
2024-08-27 16:39:29 +02:00
await mkdir(join(uploadDir, "articles"), {
recursive: true
});
for (const article of websiteData.article ?? []) {
const { head, body } = render(websiteData.content_type === "Blog" ? BlogArticle : DocsArticle, {
props: {
websiteOverview: websiteData,
article,
2024-10-04 17:09:51 +02:00
apiUrl: API_BASE_PREFIX,
websiteUrl: isPreview ? websitePreviewUrl : websiteProdUrl
}
});
await writeFile(join(uploadDir, "articles", `${article.slug}.html`), fileContents(head, body));
}
if (websiteData.legal_information) {
const { head, body } = render(websiteData.content_type === "Blog" ? BlogIndex : DocsIndex, {
props: {
websiteOverview: websiteData,
apiUrl: API_BASE_PREFIX,
2024-10-04 17:09:51 +02:00
isLegalPage: true,
websiteUrl: isPreview ? websitePreviewUrl : websiteProdUrl
}
});
2024-09-08 16:42:32 +02:00
await writeFile(join(uploadDir, "legal-information.html"), fileContents(head, body));
2024-09-08 16:42:32 +02:00
}
2024-10-04 17:09:51 +02:00
const variableStyles = await readFile(`${process.cwd()}/template-styles/variables.css`, {
encoding: "utf-8"
});
2024-08-20 19:17:05 +02:00
const commonStyles = await readFile(`${process.cwd()}/template-styles/common-styles.css`, {
encoding: "utf-8"
});
2024-08-27 16:39:29 +02:00
const specificStyles = await readFile(
`${process.cwd()}/template-styles/${websiteData.content_type.toLowerCase()}-styles.css`,
{
encoding: "utf-8"
}
);
const {
h: hDark,
s: sDark,
l: lDark
} = hexToHSL(websiteData.settings.background_color_dark_theme);
const {
h: hLight,
s: sLight,
l: lLight
} = hexToHSL(websiteData.settings.background_color_light_theme);
2024-08-25 16:31:12 +02:00
await writeFile(
2024-10-04 17:09:51 +02:00
join(uploadDir, "variables.css"),
variableStyles
.replaceAll(
/\/\* BACKGROUND_COLOR_DARK_THEME_H \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_DARK_THEME_H */ ${hDark};`
)
.replaceAll(
/\/\* BACKGROUND_COLOR_DARK_THEME_S \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_DARK_THEME_S */ ${sDark}%;`
)
.replaceAll(
/\/\* BACKGROUND_COLOR_DARK_THEME_L \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_DARK_THEME_L */ ${lDark}%;`
)
.replaceAll(
/\/\* BACKGROUND_COLOR_LIGHT_THEME_H \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_LIGHT_THEME_H */ ${hLight};`
2024-08-25 16:31:12 +02:00
)
2024-10-04 17:09:51 +02:00
.replaceAll(
/\/\* BACKGROUND_COLOR_LIGHT_THEME_S \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_LIGHT_THEME_S */ ${sLight}%;`
)
.replaceAll(
/\/\* BACKGROUND_COLOR_LIGHT_THEME_L \*\/\s*.*?;/g,
`/* BACKGROUND_COLOR_LIGHT_THEME_L */ ${lLight}%;`
)
.replaceAll(
/\/\* ACCENT_COLOR_DARK_THEME \*\/\s*.*?;/g,
`/* ACCENT_COLOR_DARK_THEME */ ${websiteData.settings.accent_color_dark_theme};`
)
.replaceAll(
/\/\* ACCENT_COLOR_LIGHT_THEME \*\/\s*.*?;/g,
`/* ACCENT_COLOR_LIGHT_THEME */ ${websiteData.settings.accent_color_light_theme};`
2024-08-25 16:31:12 +02:00
)
);
2024-10-04 17:09:51 +02:00
await writeFile(join(uploadDir, "common.css"), commonStyles);
await writeFile(join(uploadDir, "scoped.css"), specificStyles);
await setPermissions(isPreview ? join(uploadDir, "../") : uploadDir);
2024-10-04 17:09:51 +02:00
return { websitePreviewUrl, websiteProdUrl };
};
const setPermissions = async (dir: string) => {
await chmod(dir, 0o777);
const entries = await readdir(dir, { withFileTypes: true });
for (const entry of entries) {
const fullPath = join(dir, entry.name);
if (entry.isDirectory()) {
await setPermissions(fullPath);
} else {
await chmod(fullPath, 0o777);
}
}
};