From cf1600671bfbf27b5b3dd6197dac6f5044a0d79e Mon Sep 17 00:00:00 2001
From: thiloho <123883702+thiloho@users.noreply.github.com>
Date: Tue, 13 Aug 2024 22:14:47 +0200
Subject: [PATCH] Proxy API and webapp through NGINX as well
---
flake.nix | 2 +-
nix/demo-server/default.nix | 4 +-
nix/dev-vm.nix | 2 +-
nix/module.nix | 54 +++--
web-app/package.json | 4 +-
web-app/src/hooks.server.ts | 36 +--
.../routes/(anonymous)/login/+page.server.ts | 2 +-
.../(anonymous)/register/+page.server.ts | 2 +-
.../routes/(authenticated)/+page.server.ts | 65 +++---
.../(authenticated)/account/+page.server.ts | 23 +-
.../website/[websiteId]/+layout.server.ts | 34 ++-
.../website/[websiteId]/+page.server.ts | 205 ++++++++----------
.../website/[websiteId]/+page.svelte | 10 +-
.../[websiteId]/articles/+page.server.ts | 19 +-
.../articles/[articleId]/+page.server.ts | 77 +++----
.../articles/[articleId]/+page.svelte | 5 +-
.../[websiteId]/collaborators/+page.server.ts | 8 +-
.../[websiteId]/publish/+page.server.ts | 21 +-
18 files changed, 258 insertions(+), 315 deletions(-)
diff --git a/flake.nix b/flake.nix
index 8d02d74..b4e0e7a 100644
--- a/flake.nix
+++ b/flake.nix
@@ -67,7 +67,7 @@
web = {
type = "app";
program = "${pkgs.writeShellScriptBin "web-wrapper" ''
- ORIGIN=http://localhost:4000 HOST=127.0.0.1 PORT=4000 ARCHTIKA_API_PORT=3000 ARCHTIKA_NGINX_PORT=18000 ${pkgs.nodejs_22}/bin/node ${
+ ORIGIN=http://localhost:4000 HOST=127.0.0.1 PORT=4000 ${pkgs.nodejs_22}/bin/node ${
self.packages.${system}.default
}/web-app
''}/bin/web-wrapper";
diff --git a/nix/demo-server/default.nix b/nix/demo-server/default.nix
index d88bced..d5f0be7 100644
--- a/nix/demo-server/default.nix
+++ b/nix/demo-server/default.nix
@@ -27,8 +27,8 @@
networkmanager.enable = true;
firewall = {
allowedTCPPorts = [
- 10000
- 15000
+ 80
+ 443
];
};
};
diff --git a/nix/dev-vm.nix b/nix/dev-vm.nix
index d33b512..ffa742e 100644
--- a/nix/dev-vm.nix
+++ b/nix/dev-vm.nix
@@ -39,7 +39,7 @@
}
{
from = "host";
- host.port = 18000;
+ host.port = 80;
guest.port = 80;
}
];
diff --git a/nix/module.nix b/nix/module.nix
index 7e27dcf..449aeb8 100644
--- a/nix/module.nix
+++ b/nix/module.nix
@@ -50,12 +50,6 @@ in
default = 10000;
description = "Port on which the web application runs.";
};
-
- nginxPort = mkOption {
- type = types.port;
- default = 15000;
- description = "Port on which NGINX runs.";
- };
};
config = mkIf cfg.enable {
@@ -110,7 +104,7 @@ in
};
script = ''
- ORIGIN=http://localhost:${toString cfg.webAppPort} PORT=${toString cfg.webAppPort} ARCHTIKA_API_PORT=${toString cfg.port} ARCHTIKA_NGINX_PORT=${toString cfg.nginxPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
+ ORIGIN=https://demo.archtika.com PORT=${toString cfg.webAppPort} ${pkgs.nodejs_22}/bin/node ${cfg.package}/web-app
'';
};
@@ -134,24 +128,40 @@ in
recommendedProxySettings = true;
recommendedTlsSettings = true;
- virtualHosts."archtika" = {
- listen = [
- {
- addr = "0.0.0.0";
- port = cfg.nginxPort;
- }
- ];
- locations = {
- "/" = {
- root = "/var/www/archtika-websites";
- index = "index.html";
- tryFiles = "$uri $uri/ $uri/index.html =404";
- extraConfig = ''
- autoindex on;
- '';
+ virtualHosts = {
+ "demo.archtika.com" = {
+ enableACME = true;
+ forceSSL = true;
+ locations = {
+ "/" = {
+ proxyPass = "http://localhost:${toString cfg.webAppPort}";
+ };
+ "/user-websites/" = {
+ alias = "/var/www/archtika-websites/";
+ index = "index.html";
+ tryFiles = "$uri $uri/ $uri/index.html =404";
+ extraConfig = ''
+ autoindex on;
+ '';
+ };
+ "/api/" = {
+ proxyPass = "http://localhost:${toString cfg.port}/";
+ extraConfig = ''
+ default_type application/json;
+ proxy_hide_header Content-Location;
+ add_header Content-Location /api/$upstream_http_content_location;
+ proxy_set_header Connection "";
+ proxy_http_version 1.1;
+ '';
+ };
};
};
};
};
+
+ security.acme = {
+ acceptTerms = true;
+ defaults.email = "thilo.hohlt@tutanota.com";
+ };
};
}
diff --git a/web-app/package.json b/web-app/package.json
index faad2a5..2550c9d 100644
--- a/web-app/package.json
+++ b/web-app/package.json
@@ -3,9 +3,9 @@
"version": "0.0.1",
"private": true,
"scripts": {
- "dev": "ARCHTIKA_API_PORT=3000 ARCHTIKA_NGINX_PORT=18000 vite dev",
+ "dev": "vite dev",
"build": "vite build",
- "preview": "ARCHTIKA_API_PORT=3000 ARCHTIKA_NGINX_PORT=18000 vite preview",
+ "preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --check .",
diff --git a/web-app/src/hooks.server.ts b/web-app/src/hooks.server.ts
index 9669b38..d7fda69 100644
--- a/web-app/src/hooks.server.ts
+++ b/web-app/src/hooks.server.ts
@@ -1,27 +1,29 @@
import { redirect } from "@sveltejs/kit";
export const handle = async ({ event, resolve }) => {
- const userData = await event.fetch(`http://localhost:${process.env.ARCHTIKA_API_PORT}/account`, {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${event.cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
- });
+ if (!event.url.pathname.startsWith("/api/")) {
+ const userData = await event.fetch(`/api/account`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${event.cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
+ }
+ });
- if (!userData.ok && !["/login", "/register"].includes(event.url.pathname)) {
- throw redirect(303, "/login");
- }
-
- if (userData.ok) {
- if (["/login", "/register"].includes(event.url.pathname)) {
- throw redirect(303, "/");
+ if (!userData.ok && !["/login", "/register"].includes(event.url.pathname)) {
+ throw redirect(303, "/login");
}
- const user = await userData.json();
+ if (userData.ok) {
+ if (["/login", "/register"].includes(event.url.pathname)) {
+ throw redirect(303, "/");
+ }
- event.locals.user = user;
+ const user = await userData.json();
+
+ event.locals.user = user;
+ }
}
return await resolve(event);
diff --git a/web-app/src/routes/(anonymous)/login/+page.server.ts b/web-app/src/routes/(anonymous)/login/+page.server.ts
index 18dfdfc..b7994c1 100644
--- a/web-app/src/routes/(anonymous)/login/+page.server.ts
+++ b/web-app/src/routes/(anonymous)/login/+page.server.ts
@@ -4,7 +4,7 @@ export const actions: Actions = {
default: async ({ request, cookies, fetch }) => {
const data = await request.formData();
- const res = await fetch(`http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/login`, {
+ const res = await fetch(`/api/rpc/login`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
diff --git a/web-app/src/routes/(anonymous)/register/+page.server.ts b/web-app/src/routes/(anonymous)/register/+page.server.ts
index 1e6f447..35c87dc 100644
--- a/web-app/src/routes/(anonymous)/register/+page.server.ts
+++ b/web-app/src/routes/(anonymous)/register/+page.server.ts
@@ -4,7 +4,7 @@ export const actions: Actions = {
default: async ({ request, fetch }) => {
const data = await request.formData();
- const res = await fetch(`http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/register`, {
+ const res = await fetch(`/api/rpc/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
diff --git a/web-app/src/routes/(authenticated)/+page.server.ts b/web-app/src/routes/(authenticated)/+page.server.ts
index bc0d0eb..17985b8 100644
--- a/web-app/src/routes/(authenticated)/+page.server.ts
+++ b/web-app/src/routes/(authenticated)/+page.server.ts
@@ -6,7 +6,7 @@ export const load: PageServerLoad = async ({ fetch, cookies, url }) => {
const params = new URLSearchParams();
- const baseFetchUrl = `http://localhost:${process.env.ARCHTIKA_API_PORT}/website`;
+ const baseFetchUrl = `/api/website`;
if (searchQuery) {
params.append("title", `ilike.*${searchQuery}*`);
@@ -63,20 +63,17 @@ export const actions: Actions = {
createWebsite: async ({ request, fetch, cookies }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/create_website`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- content_type: data.get("content-type"),
- title: data.get("title")
- })
- }
- );
+ const res = await fetch(`/api/rpc/create_website`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ content_type: data.get("content-type"),
+ title: data.get("title")
+ })
+ });
if (!res.ok) {
const response = await res.json();
@@ -88,19 +85,16 @@ export const actions: Actions = {
updateWebsite: async ({ request, cookies, fetch }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/website?id=eq.${data.get("id")}`,
- {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- title: data.get("title")
- })
- }
- );
+ const res = await fetch(`/api/website?id=eq.${data.get("id")}`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ title: data.get("title")
+ })
+ });
if (!res.ok) {
const response = await res.json();
@@ -112,16 +106,13 @@ export const actions: Actions = {
deleteWebsite: async ({ request, cookies, fetch }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/website?id=eq.${data.get("id")}`,
- {
- method: "DELETE",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- }
+ const res = await fetch(`/api/website?id=eq.${data.get("id")}`, {
+ method: "DELETE",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
}
- );
+ });
if (!res.ok) {
const response = await res.json();
diff --git a/web-app/src/routes/(authenticated)/account/+page.server.ts b/web-app/src/routes/(authenticated)/account/+page.server.ts
index 6e1be1a..adc0862 100644
--- a/web-app/src/routes/(authenticated)/account/+page.server.ts
+++ b/web-app/src/routes/(authenticated)/account/+page.server.ts
@@ -15,19 +15,16 @@ export const actions: Actions = {
deleteAccount: async ({ request, fetch, cookies }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/delete_account`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- password: data.get("password")
- })
- }
- );
+ const res = await fetch(`/api/rpc/delete_account`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ password: data.get("password")
+ })
+ });
const response = await res.json();
diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+layout.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/+layout.server.ts
index 630189f..94a0549 100644
--- a/web-app/src/routes/(authenticated)/website/[websiteId]/+layout.server.ts
+++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+layout.server.ts
@@ -1,29 +1,23 @@
import type { LayoutServerLoad } from "./$types";
export const load: LayoutServerLoad = async ({ params, fetch, cookies }) => {
- const websiteData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/website?id=eq.${params.websiteId}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
+ const websiteData = await fetch(`/api/website?id=eq.${params.websiteId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
}
- );
+ });
- const homeData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/home?website_id=eq.${params.websiteId}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
+ const homeData = await fetch(`/api/home?website_id=eq.${params.websiteId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
}
- );
+ });
const website = await websiteData.json();
const home = await homeData.json();
diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts
index cd2d1d1..04054a5 100644
--- a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts
+++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.server.ts
@@ -1,41 +1,32 @@
import type { Actions, PageServerLoad } from "./$types";
export const load: PageServerLoad = async ({ params, fetch, cookies, url }) => {
- const globalSettingsData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/settings?website_id=eq.${params.websiteId}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
+ const globalSettingsData = await fetch(`/api/settings?website_id=eq.${params.websiteId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
}
- );
+ });
- const headerData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/header?website_id=eq.${params.websiteId}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
+ const headerData = await fetch(`/api/header?website_id=eq.${params.websiteId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
}
- );
+ });
- const footerData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/footer?website_id=eq.${params.websiteId}`,
- {
- method: "GET",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json"
- }
+ const footerData = await fetch(`/api/footer?website_id=eq.${params.websiteId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json"
}
- );
+ });
const globalSettings = await globalSettingsData.json();
const header = await headerData.json();
@@ -53,21 +44,18 @@ export const actions: Actions = {
const data = await request.formData();
const faviconFile = data.get("favicon") as File;
- const uploadedImageData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/upload_file`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/octet-stream",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json",
- "X-Website-Id": params.websiteId,
- "X-Mimetype": faviconFile.type,
- "X-Original-Filename": faviconFile.name
- },
- body: await faviconFile.arrayBuffer()
- }
- );
+ const uploadedImageData = await fetch(`/api/rpc/upload_file`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/octet-stream",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json",
+ "X-Website-Id": params.websiteId,
+ "X-Mimetype": faviconFile.type,
+ "X-Original-Filename": faviconFile.name
+ },
+ body: await faviconFile.arrayBuffer()
+ });
const uploadedImage = await uploadedImageData.json();
@@ -75,21 +63,18 @@ export const actions: Actions = {
return { success: false, message: uploadedImage.message };
}
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/settings?website_id=eq.${params.websiteId}`,
- {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- accent_color_light_theme: data.get("accent-color-light"),
- accent_color_dark_theme: data.get("accent-color-dark"),
- favicon_image: uploadedImage.file_id
- })
- }
- );
+ const res = await fetch(`/api/settings?website_id=eq.${params.websiteId}`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ accent_color_light_theme: data.get("accent-color-light"),
+ accent_color_dark_theme: data.get("accent-color-dark"),
+ favicon_image: uploadedImage.file_id
+ })
+ });
if (!res.ok) {
const response = await res.json();
@@ -105,21 +90,18 @@ export const actions: Actions = {
const data = await request.formData();
const logoImage = data.get("logo-image") as File;
- const uploadedImageData = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/rpc/upload_file`,
- {
- method: "POST",
- headers: {
- "Content-Type": "application/octet-stream",
- Authorization: `Bearer ${cookies.get("session_token")}`,
- Accept: "application/vnd.pgrst.object+json",
- "X-Website-Id": params.websiteId,
- "X-Mimetype": logoImage.type,
- "X-Original-Filename": logoImage.name
- },
- body: await logoImage.arrayBuffer()
- }
- );
+ const uploadedImageData = await fetch(`/api/rpc/upload_file`, {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/octet-stream",
+ Authorization: `Bearer ${cookies.get("session_token")}`,
+ Accept: "application/vnd.pgrst.object+json",
+ "X-Website-Id": params.websiteId,
+ "X-Mimetype": logoImage.type,
+ "X-Original-Filename": logoImage.name
+ },
+ body: await logoImage.arrayBuffer()
+ });
const uploadedImage = await uploadedImageData.json();
@@ -127,21 +109,18 @@ export const actions: Actions = {
return { success: false, message: uploadedImage.message };
}
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/header?website_id=eq.${params.websiteId}`,
- {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- logo_type: data.get("logo-type"),
- logo_text: data.get("logo-text"),
- logo_image: uploadedImage.file_id
- })
- }
- );
+ const res = await fetch(`/api/header?website_id=eq.${params.websiteId}`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ logo_type: data.get("logo-type"),
+ logo_text: data.get("logo-text"),
+ logo_image: uploadedImage.file_id
+ })
+ });
if (!res.ok) {
const response = await res.json();
@@ -156,19 +135,16 @@ export const actions: Actions = {
updateHome: async ({ request, fetch, cookies, params }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/home?website_id=eq.${params.websiteId}`,
- {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- main_content: data.get("main-content")
- })
- }
- );
+ const res = await fetch(`/api/home?website_id=eq.${params.websiteId}`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ main_content: data.get("main-content")
+ })
+ });
if (!res.ok) {
const response = await res.json();
@@ -180,19 +156,16 @@ export const actions: Actions = {
updateFooter: async ({ request, fetch, cookies, params }) => {
const data = await request.formData();
- const res = await fetch(
- `http://localhost:${process.env.ARCHTIKA_API_PORT}/footer?website_id=eq.${params.websiteId}`,
- {
- method: "PATCH",
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${cookies.get("session_token")}`
- },
- body: JSON.stringify({
- additional_text: data.get("additional-text")
- })
- }
- );
+ const res = await fetch(`/api/footer?website_id=eq.${params.websiteId}`, {
+ method: "PATCH",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${cookies.get("session_token")}`
+ },
+ body: JSON.stringify({
+ additional_text: data.get("additional-text")
+ })
+ });
if (!res.ok) {
const response = await res.json();
diff --git a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte
index 093cad8..5bca128 100644
--- a/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte
+++ b/web-app/src/routes/(authenticated)/website/[websiteId]/+page.svelte
@@ -55,10 +55,7 @@
{#if data.globalSettings.favicon_image}
+
+
+
`
+ `
`
)
.replace("{{title}}", `
${article.publication_date}
`)