diff --git a/rest-api/db/migrations/20240825133549_adjust_upload_function.sql b/rest-api/db/migrations/20240825133549_adjust_upload_function.sql
new file mode 100644
index 0000000..b15e618
--- /dev/null
+++ b/rest-api/db/migrations/20240825133549_adjust_upload_function.sql
@@ -0,0 +1,74 @@
+-- migrate:up
+CREATE OR REPLACE FUNCTION api.upload_file (BYTEA, OUT file_id UUID)
+AS $$
+DECLARE
+ _headers JSON := CURRENT_SETTING('request.headers', TRUE)::JSON;
+ _website_id UUID := (_headers ->> 'x-website-id')::UUID;
+ _mimetype TEXT := _headers ->> 'x-mimetype';
+ _original_filename TEXT := _headers ->> 'x-original-filename';
+ _allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/jpeg', 'image/webp'];
+ _max_file_size INT := 5 * 1024 * 1024;
+BEGIN
+ IF OCTET_LENGTH($1) = 0 THEN
+ RAISE invalid_parameter_value
+ USING message = 'No file data was provided';
+ END IF;
+ IF _mimetype IS NULL OR _mimetype NOT IN (
+ SELECT
+ UNNEST(_allowed_mimetypes)) THEN
+ RAISE invalid_parameter_value
+ USING message = 'Invalid MIME type. Allowed types are: png, jpg, webp';
+ END IF;
+ IF OCTET_LENGTH($1) > _max_file_size THEN
+ RAISE program_limit_exceeded
+ USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
+ END IF;
+ INSERT INTO internal.media (website_id, blob, mimetype, original_name)
+ VALUES (_website_id, $1, _mimetype, _original_filename)
+ RETURNING
+ id INTO file_id;
+END;
+$$
+LANGUAGE plpgsql
+SECURITY DEFINER;
+
+GRANT EXECUTE ON FUNCTION api.upload_file (BYTEA) TO authenticated_user;
+
+-- migrate:down
+
+DROP FUNCTION api.upload_file(BYTEA);
+
+CREATE FUNCTION api.upload_file (BYTEA, OUT file_id UUID)
+AS $$
+DECLARE
+ _headers JSON := CURRENT_SETTING('request.headers', TRUE)::JSON;
+ _website_id UUID := (_headers ->> 'x-website-id')::UUID;
+ _mimetype TEXT := _headers ->> 'x-mimetype';
+ _original_filename TEXT := _headers ->> 'x-original-filename';
+ _allowed_mimetypes TEXT[] := ARRAY['image/png', 'image/jpeg', 'image/webp'];
+ _max_file_size INT := 5 * 1024 * 1024;
+BEGIN
+ IF OCTET_LENGTH($1) = 0 THEN
+ RAISE invalid_parameter_value
+ USING message = 'No file data was provided';
+ END IF;
+ IF _mimetype IS NULL OR _mimetype NOT IN (
+ SELECT
+ UNNEST(_allowed_mimetypes)) THEN
+ RAISE invalid_parameter_value
+ USING message = 'Invalid MIME type. Allowed types are: png, svg, jpg, webp';
+ END IF;
+ IF OCTET_LENGTH($1) > _max_file_size THEN
+ RAISE program_limit_exceeded
+ USING message = FORMAT('File size exceeds the maximum limit of %s MB', _max_file_size / (1024 * 1024));
+ END IF;
+ INSERT INTO internal.media (website_id, blob, mimetype, original_name)
+ VALUES (_website_id, $1, _mimetype, _original_filename)
+ RETURNING
+ id INTO file_id;
+END;
+$$
+LANGUAGE plpgsql
+SECURITY DEFINER;
+
+GRANT EXECUTE ON FUNCTION api.upload_file (BYTEA) TO authenticated_user;
\ No newline at end of file
diff --git a/web-app/src/lib/templates/blog/BlogArticle.svelte b/web-app/src/lib/templates/blog/BlogArticle.svelte
index 3ce2ebf..9e076d1 100644
--- a/web-app/src/lib/templates/blog/BlogArticle.svelte
+++ b/web-app/src/lib/templates/blog/BlogArticle.svelte
@@ -4,6 +4,7 @@
import BlogFooter from "./common/BlogFooter.svelte";
const {
+ favicon,
title,
logoType,
logo,
@@ -12,6 +13,7 @@
publicationDate,
footerAdditionalText
}: {
+ favicon: string;
title: string;
logoType: "text" | "image";
logo: string;
@@ -22,7 +24,7 @@
} = $props();
-
+
diff --git a/web-app/src/lib/templates/blog/BlogIndex.svelte b/web-app/src/lib/templates/blog/BlogIndex.svelte
index 5852c4a..9035ec9 100644
--- a/web-app/src/lib/templates/blog/BlogIndex.svelte
+++ b/web-app/src/lib/templates/blog/BlogIndex.svelte
@@ -4,6 +4,7 @@
import BlogFooter from "./common/BlogFooter.svelte";
const {
+ favicon,
title,
logoType,
logo,
@@ -11,6 +12,7 @@
articles,
footerAdditionalText
}: {
+ favicon: string;
title: string;
logoType: "text" | "image";
logo: string;
@@ -20,7 +22,7 @@
} = $props();
-
+
diff --git a/web-app/src/lib/templates/blog/common/BlogHead.svelte b/web-app/src/lib/templates/blog/common/BlogHead.svelte
index 353c445..c0dece4 100644
--- a/web-app/src/lib/templates/blog/common/BlogHead.svelte
+++ b/web-app/src/lib/templates/blog/common/BlogHead.svelte
@@ -1,5 +1,9 @@
@@ -8,5 +12,8 @@
{title}
+ {#if favicon}
+
+ {/if}
diff --git a/web-app/src/lib/templates/blog/common/BlogNav.svelte b/web-app/src/lib/templates/blog/common/BlogNav.svelte
index 6542061..f1f7218 100644
--- a/web-app/src/lib/templates/blog/common/BlogNav.svelte
+++ b/web-app/src/lib/templates/blog/common/BlogNav.svelte
@@ -10,7 +10,7 @@
{logo}
{:else}
-
+
{/if}
diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts
index 3867897..814a4dc 100644
--- a/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts
+++ b/web-app/src/routes/(authenticated)/website/[websiteId]/publish/+page.server.ts
@@ -57,9 +57,15 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
{
({ head, body } = render(BlogIndex, {
props: {
+ favicon: websiteData.favicon_image
+ ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
+ : "",
title: websiteData.title,
logoType: websiteData.logo_type,
- logo: websiteData.logo_text,
+ logo:
+ websiteData.logo_type === "text"
+ ? websiteData.logo_text
+ : `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
mainContent: md(websiteData.main_content ?? "", false),
articles: websiteData.articles ?? [],
footerAdditionalText: md(websiteData.additional_text ?? "")
@@ -110,9 +116,15 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
{
({ head, body } = render(BlogArticle, {
props: {
+ favicon: websiteData.favicon_image
+ ? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
+ : "",
title: article.title,
logoType: websiteData.logo_type,
- logo: websiteData.logo_text,
+ logo:
+ websiteData.logo_type === "text"
+ ? websiteData.logo_text
+ : `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
coverImage: article.cover_image
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${article.cover_image}`
: "",
@@ -160,5 +172,21 @@ const generateStaticFiles = async (websiteData: any, isPreview: boolean = true)
const specificStyles = await readFile(`${process.cwd()}/template-styles/blog-styles.css`, {
encoding: "utf-8"
});
- await writeFile(join(uploadDir, "styles.css"), commonStyles.concat(specificStyles));
+ await writeFile(
+ join(uploadDir, "styles.css"),
+ commonStyles
+ .concat(specificStyles)
+ .replace(
+ /--color-accent:\s*(.*?);/,
+ `--color-accent: ${websiteData.accent_color_dark_theme};`
+ )
+ .replace(
+ /@media\s*\(prefers-color-scheme:\s*dark\)\s*{[^}]*--color-accent:\s*(.*?);/,
+ (match) =>
+ match.replace(
+ /--color-accent:\s*(.*?);/,
+ `--color-accent: ${websiteData.accent_color_light_theme};`
+ )
+ )
+ );
};
diff --git a/web-app/template-styles/common-styles.css b/web-app/template-styles/common-styles.css
index ba8ee76..631d7f8 100644
--- a/web-app/template-styles/common-styles.css
+++ b/web-app/template-styles/common-styles.css
@@ -166,7 +166,7 @@ summary {
background-color: var(--bg-tertiary);
}
-:is(button, input, textarea, select, a, summary):focus,
+:is(button, input, textarea, select, a, summary, pre):focus,
#toggle-mobile-preview:checked + label {
outline: 0.125rem solid var(--color-accent);
outline-offset: 0.25rem;