mirror of
https://github.com/thiloho/archtika.git
synced 2025-11-22 02:41:35 +01:00
Add legal information operation site
This commit is contained in:
@@ -68,7 +68,7 @@
|
|||||||
];
|
];
|
||||||
locations = {
|
locations = {
|
||||||
"/" = {
|
"/" = {
|
||||||
root = "/var/www/archtika-websites";
|
root = "/var/www/archtika-websites/";
|
||||||
index = "index.html";
|
index = "index.html";
|
||||||
tryFiles = "$uri $uri/ $uri.html $uri/index.html index.html =404";
|
tryFiles = "$uri $uri/ $uri.html $uri/index.html index.html =404";
|
||||||
extraConfig = ''
|
extraConfig = ''
|
||||||
|
|||||||
55
rest-api/db/migrations/20240908104458_extra_page_table.sql
Normal file
55
rest-api/db/migrations/20240908104458_extra_page_table.sql
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
-- migrate:up
|
||||||
|
CREATE TABLE internal.legal_information (
|
||||||
|
website_id UUID PRIMARY KEY REFERENCES internal.website (id) ON DELETE CASCADE,
|
||||||
|
main_content TEXT NOT NULL CHECK (TRIM(main_content) != ''),
|
||||||
|
last_modified_at TIMESTAMPTZ NOT NULL DEFAULT CLOCK_TIMESTAMP(),
|
||||||
|
last_modified_by UUID REFERENCES internal.user (id) ON DELETE SET NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE VIEW api.legal_information WITH ( security_invoker = ON
|
||||||
|
) AS
|
||||||
|
SELECT
|
||||||
|
website_id,
|
||||||
|
main_content,
|
||||||
|
last_modified_at,
|
||||||
|
last_modified_by
|
||||||
|
FROM
|
||||||
|
internal.legal_information;
|
||||||
|
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE ON internal.legal_information TO authenticated_user;
|
||||||
|
|
||||||
|
GRANT SELECT, INSERT, UPDATE, DELETE ON api.legal_information TO authenticated_user;
|
||||||
|
|
||||||
|
ALTER TABLE internal.legal_information ENABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
CREATE POLICY view_legal_information ON internal.legal_information
|
||||||
|
FOR SELECT
|
||||||
|
USING (internal.user_has_website_access (website_id, 10));
|
||||||
|
|
||||||
|
CREATE POLICY update_legal_information ON internal.legal_information
|
||||||
|
FOR UPDATE
|
||||||
|
USING (internal.user_has_website_access (website_id, 30));
|
||||||
|
|
||||||
|
CREATE POLICY delete_legal_information ON internal.legal_information
|
||||||
|
FOR DELETE
|
||||||
|
USING (internal.user_has_website_access (website_id, 30));
|
||||||
|
|
||||||
|
CREATE POLICY insert_legal_information ON internal.legal_information
|
||||||
|
FOR INSERT
|
||||||
|
WITH CHECK (internal.user_has_website_access (website_id, 30));
|
||||||
|
|
||||||
|
-- migrate:down
|
||||||
|
DROP POLICY insert_legal_information ON internal.legal_information;
|
||||||
|
|
||||||
|
DROP POLICY delete_legal_information ON internal.legal_information;
|
||||||
|
|
||||||
|
DROP POLICY update_legal_information ON internal.legal_information;
|
||||||
|
|
||||||
|
DROP POLICY view_legal_information ON internal.legal_information;
|
||||||
|
|
||||||
|
ALTER TABLE internal.legal_information DISABLE ROW LEVEL SECURITY;
|
||||||
|
|
||||||
|
DROP VIEW api.legal_information;
|
||||||
|
|
||||||
|
DROP TABLE internal.legal_information;
|
||||||
|
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
-- migrate:up
|
||||||
|
CREATE OR REPLACE VIEW api.website_overview WITH ( security_invoker = ON
|
||||||
|
) AS
|
||||||
|
SELECT
|
||||||
|
w.id,
|
||||||
|
w.user_id,
|
||||||
|
w.content_type,
|
||||||
|
w.title,
|
||||||
|
s.accent_color_light_theme,
|
||||||
|
s.accent_color_dark_theme,
|
||||||
|
s.favicon_image,
|
||||||
|
h.logo_type,
|
||||||
|
h.logo_text,
|
||||||
|
h.logo_image,
|
||||||
|
ho.main_content,
|
||||||
|
f.additional_text,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM
|
||||||
|
internal.article a
|
||||||
|
WHERE
|
||||||
|
a.website_id = w.id
|
||||||
|
) AS articles,
|
||||||
|
CASE WHEN w.content_type = 'Docs' THEN
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
JSON_OBJECT_AGG(
|
||||||
|
COALESCE(
|
||||||
|
category_name, 'Uncategorized'
|
||||||
|
), articles
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
dc.category_name,
|
||||||
|
dc.category_weight AS category_weight,
|
||||||
|
JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at
|
||||||
|
) ORDER BY a.article_weight DESC NULLS LAST
|
||||||
|
) AS articles
|
||||||
|
FROM
|
||||||
|
internal.article a
|
||||||
|
LEFT JOIN internal.docs_category dc ON a.category = dc.id
|
||||||
|
WHERE
|
||||||
|
a.website_id = w.id
|
||||||
|
GROUP BY
|
||||||
|
dc.id,
|
||||||
|
dc.category_name,
|
||||||
|
dc.category_weight
|
||||||
|
ORDER BY
|
||||||
|
category_weight DESC NULLS LAST
|
||||||
|
) AS categorized_articles)
|
||||||
|
ELSE
|
||||||
|
NULL
|
||||||
|
END AS categorized_articles,
|
||||||
|
li.main_content legal_information_main_content
|
||||||
|
FROM
|
||||||
|
internal.website w
|
||||||
|
JOIN internal.settings s ON w.id = s.website_id
|
||||||
|
JOIN internal.header h ON w.id = h.website_id
|
||||||
|
JOIN internal.home ho ON w.id = ho.website_id
|
||||||
|
JOIN internal.footer f ON w.id = f.website_id
|
||||||
|
LEFT JOIN internal.legal_information li ON w.id = li.website_id;
|
||||||
|
|
||||||
|
GRANT SELECT ON api.website_overview TO authenticated_user;
|
||||||
|
|
||||||
|
-- migrate:down
|
||||||
|
DROP VIEW api.website_overview;
|
||||||
|
|
||||||
|
CREATE VIEW api.website_overview WITH ( security_invoker = ON
|
||||||
|
) AS
|
||||||
|
SELECT
|
||||||
|
w.id,
|
||||||
|
w.user_id,
|
||||||
|
w.content_type,
|
||||||
|
w.title,
|
||||||
|
s.accent_color_light_theme,
|
||||||
|
s.accent_color_dark_theme,
|
||||||
|
s.favicon_image,
|
||||||
|
h.logo_type,
|
||||||
|
h.logo_text,
|
||||||
|
h.logo_image,
|
||||||
|
ho.main_content,
|
||||||
|
f.additional_text,
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at
|
||||||
|
)
|
||||||
|
)
|
||||||
|
FROM
|
||||||
|
internal.article a
|
||||||
|
WHERE
|
||||||
|
a.website_id = w.id
|
||||||
|
) AS articles,
|
||||||
|
CASE WHEN w.content_type = 'Docs' THEN
|
||||||
|
(
|
||||||
|
SELECT
|
||||||
|
JSON_OBJECT_AGG(
|
||||||
|
COALESCE(
|
||||||
|
category_name, 'Uncategorized'
|
||||||
|
), articles
|
||||||
|
)
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
dc.category_name,
|
||||||
|
dc.category_weight AS category_weight,
|
||||||
|
JSON_AGG(
|
||||||
|
JSON_BUILD_OBJECT(
|
||||||
|
'id', a.id, 'title', a.title, 'meta_description', a.meta_description, 'meta_author', a.meta_author, 'cover_image', a.cover_image, 'publication_date', a.publication_date, 'main_content', a.main_content, 'created_at', a.created_at, 'last_modified_at', a.last_modified_at
|
||||||
|
) ORDER BY a.article_weight DESC NULLS LAST
|
||||||
|
) AS articles
|
||||||
|
FROM
|
||||||
|
internal.article a
|
||||||
|
LEFT JOIN internal.docs_category dc ON a.category = dc.id
|
||||||
|
WHERE
|
||||||
|
a.website_id = w.id
|
||||||
|
GROUP BY
|
||||||
|
dc.id,
|
||||||
|
dc.category_name,
|
||||||
|
dc.category_weight
|
||||||
|
ORDER BY
|
||||||
|
category_weight DESC NULLS LAST
|
||||||
|
) AS categorized_articles)
|
||||||
|
ELSE
|
||||||
|
NULL
|
||||||
|
END AS categorized_articles
|
||||||
|
FROM
|
||||||
|
internal.website w
|
||||||
|
JOIN internal.settings s ON w.id = s.website_id
|
||||||
|
JOIN internal.header h ON w.id = h.website_id
|
||||||
|
JOIN internal.home ho ON w.id = ho.website_id
|
||||||
|
JOIN internal.footer f ON w.id = f.website_id;
|
||||||
|
|
||||||
|
GRANT SELECT ON api.website_overview TO authenticated_user;
|
||||||
|
|
||||||
@@ -49,6 +49,9 @@
|
|||||||
<li>
|
<li>
|
||||||
<a href="/website/{id}/collaborators">Collaborators</a>
|
<a href="/website/{id}/collaborators">Collaborators</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/website/{id}/legal-information">Legal information</a>
|
||||||
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="/website/{id}/publish">Publish</a>
|
<a href="/website/{id}/publish">Publish</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -50,4 +50,4 @@
|
|||||||
</main>
|
</main>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer text={footerAdditionalText} />
|
<Footer text={footerAdditionalText} isIndexPage={false} />
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
const { text }: { text: string } = $props();
|
const { text, isIndexPage = true }: { text: string; isIndexPage?: boolean } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<small>
|
<small>
|
||||||
{@html text}
|
{@html text.replace(
|
||||||
|
"!!legal",
|
||||||
|
`<a href="${isIndexPage ? "./legal-information" : "../legal-information"}">Legal information</a>`
|
||||||
|
)}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -42,4 +42,4 @@
|
|||||||
</main>
|
</main>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<Footer text={footerAdditionalText} />
|
<Footer text={footerAdditionalText} isIndexPage={false} />
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ export const md = (markdownContent: string, showToc = true) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => {
|
export const handleImagePaste = async (event: ClipboardEvent, API_BASE_PREFIX: string) => {
|
||||||
const clipboardItems = Array.from(event.clipboardData?.items || []);
|
const clipboardItems = Array.from(event.clipboardData?.items ?? []);
|
||||||
const file = clipboardItems.find((item) => item.type.startsWith("image/"));
|
const file = clipboardItems.find((item) => item.type.startsWith("image/"));
|
||||||
|
|
||||||
if (!file) return null;
|
if (!file) return null;
|
||||||
|
|||||||
@@ -39,7 +39,8 @@
|
|||||||
id={data.website.id}
|
id={data.website.id}
|
||||||
contentType={data.website.content_type}
|
contentType={data.website.content_type}
|
||||||
title={data.website.title}
|
title={data.website.title}
|
||||||
{previewContent}
|
previewContent={previewContent ||
|
||||||
|
"Put some markdown content in main content to see a live preview here"}
|
||||||
previewScrollTop={textareaScrollTop}
|
previewScrollTop={textareaScrollTop}
|
||||||
>
|
>
|
||||||
<section id="global">
|
<section id="global">
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type { Actions, PageServerLoad } from "./$types";
|
||||||
|
import { API_BASE_PREFIX } from "$lib/server/utils";
|
||||||
|
import { rm } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
export const load: PageServerLoad = async ({ parent, fetch, params, cookies }) => {
|
||||||
|
const legalInformationData = await fetch(
|
||||||
|
`${API_BASE_PREFIX}/legal_information?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 legalInformation = legalInformationData.ok ? await legalInformationData.json() : null;
|
||||||
|
const { website } = await parent();
|
||||||
|
|
||||||
|
return {
|
||||||
|
legalInformation,
|
||||||
|
website
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const actions: Actions = {
|
||||||
|
createUpdateLegalInformation: async ({ request, fetch, cookies, params }) => {
|
||||||
|
const data = await request.formData();
|
||||||
|
|
||||||
|
const res = await fetch(`${API_BASE_PREFIX}/legal_information`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${cookies.get("session_token")}`,
|
||||||
|
Prefer: "resolution=merge-duplicates",
|
||||||
|
Accept: "application/vnd.pgrst.object+json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
website_id: params.websiteId,
|
||||||
|
main_content: data.get("main-content")
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const response = await res.json();
|
||||||
|
return { success: false, message: response.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: `Successfully ${res.status === 201 ? "created" : "updated"} legal information`
|
||||||
|
};
|
||||||
|
},
|
||||||
|
deleteLegalInformation: async ({ fetch, cookies, params }) => {
|
||||||
|
const res = await fetch(
|
||||||
|
`${API_BASE_PREFIX}/legal_information?website_id=eq.${params.websiteId}`,
|
||||||
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Bearer ${cookies.get("session_token")}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
const response = await res.json();
|
||||||
|
return { success: false, message: response.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
await rm(
|
||||||
|
join("/", "var", "www", "archtika-websites", params.websiteId, "legal-information.html"),
|
||||||
|
{ force: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
return { success: true, message: `Successfully deleted legal information` };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { enhance } from "$app/forms";
|
||||||
|
import WebsiteEditor from "$lib/components/WebsiteEditor.svelte";
|
||||||
|
import SuccessOrError from "$lib/components/SuccessOrError.svelte";
|
||||||
|
import LoadingSpinner from "$lib/components/LoadingSpinner.svelte";
|
||||||
|
import Modal from "$lib/components/Modal.svelte";
|
||||||
|
import type { ActionData, PageServerData } from "./$types";
|
||||||
|
|
||||||
|
const { data, form }: { data: PageServerData; form: ActionData } = $props();
|
||||||
|
|
||||||
|
let previewContent = $state(data.legalInformation?.main_content);
|
||||||
|
let mainContentTextarea: HTMLTextAreaElement;
|
||||||
|
let textareaScrollTop = $state(0);
|
||||||
|
|
||||||
|
const updateScrollPercentage = () => {
|
||||||
|
const { scrollTop, scrollHeight, clientHeight } = mainContentTextarea;
|
||||||
|
textareaScrollTop = (scrollTop / (scrollHeight - clientHeight)) * 100;
|
||||||
|
};
|
||||||
|
|
||||||
|
let sending = $state(false);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<SuccessOrError success={form?.success} message={form?.message} />
|
||||||
|
|
||||||
|
{#if sending}
|
||||||
|
<LoadingSpinner />
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<WebsiteEditor
|
||||||
|
id={data.website.id}
|
||||||
|
contentType={data.website.content_type}
|
||||||
|
title={data.website.title}
|
||||||
|
previewContent={previewContent ||
|
||||||
|
"Put some markdown content in main content to see a live preview here"}
|
||||||
|
previewScrollTop={textareaScrollTop}
|
||||||
|
>
|
||||||
|
<section id="legal-information">
|
||||||
|
<h2>
|
||||||
|
<a href="#legal-information">Legal information</a>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Static websites that do not collect user data and do not use cookies generally have minimal
|
||||||
|
legal obligations regarding privacy policies, imprints, etc. However, it may still be a good
|
||||||
|
idea to include, for example:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<ol>
|
||||||
|
<li>A simple privacy policy stating that no personal information is collected or stored</li>
|
||||||
|
<li>
|
||||||
|
An imprint (if required by local law) with contact information for the site owner/operator
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
|
||||||
|
<p>Always consult local laws and regulations for specific requirements in your jurisdiction.</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To include a link to your legal information in the footer, you can write <code>!!legal</code>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<form
|
||||||
|
method="POST"
|
||||||
|
action="?/createUpdateLegalInformation"
|
||||||
|
use:enhance={() => {
|
||||||
|
sending = true;
|
||||||
|
return async ({ update }) => {
|
||||||
|
await update({ reset: false });
|
||||||
|
sending = false;
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<label>
|
||||||
|
Main content:
|
||||||
|
<textarea
|
||||||
|
name="main-content"
|
||||||
|
rows="20"
|
||||||
|
placeholder="## Impressum
|
||||||
|
|
||||||
|
## Privacy policy"
|
||||||
|
bind:value={previewContent}
|
||||||
|
bind:this={mainContentTextarea}
|
||||||
|
onscroll={updateScrollPercentage}
|
||||||
|
required>{data.legalInformation?.main_content ?? ""}</textarea
|
||||||
|
>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="submit">Submit</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{#if data.legalInformation?.main_content}
|
||||||
|
<Modal id="delete-legal-information" text="Delete">
|
||||||
|
<form
|
||||||
|
action="?/deleteLegalInformation"
|
||||||
|
method="post"
|
||||||
|
use:enhance={() => {
|
||||||
|
sending = true;
|
||||||
|
return async ({ update }) => {
|
||||||
|
await update();
|
||||||
|
window.location.hash = "!";
|
||||||
|
sending = false;
|
||||||
|
previewContent = null;
|
||||||
|
};
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h3>Delete legal information</h3>
|
||||||
|
<p>
|
||||||
|
<strong>Caution!</strong>
|
||||||
|
This action will remove the legal information page from the website and delete all data.
|
||||||
|
</p>
|
||||||
|
<button type="submit">Delete legal information</button>
|
||||||
|
</form>
|
||||||
|
</Modal>
|
||||||
|
{/if}
|
||||||
|
</section>
|
||||||
|
</WebsiteEditor>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
form[action="?/createUpdateLegalInformation"] {
|
||||||
|
margin-block-start: var(--space-s);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,6 +32,7 @@ interface WebsiteData {
|
|||||||
categorized_articles: {
|
categorized_articles: {
|
||||||
[key: string]: { title: string; publication_date: string; meta_description: string }[];
|
[key: string]: { title: string; publication_date: string; meta_description: string }[];
|
||||||
};
|
};
|
||||||
|
legal_information_main_content: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, fetch, cookies, parent }) => {
|
export const load: PageServerLoad = async ({ params, fetch, cookies, parent }) => {
|
||||||
@@ -251,6 +252,67 @@ const generateStaticFiles = async (websiteData: WebsiteData, isPreview: boolean
|
|||||||
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
|
await writeFile(join(uploadDir, "articles", `${articleFileName}.html`), articleFileContents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (websiteData.legal_information_main_content) {
|
||||||
|
let head = "";
|
||||||
|
let body = "";
|
||||||
|
|
||||||
|
switch (websiteData.content_type) {
|
||||||
|
case "Blog":
|
||||||
|
{
|
||||||
|
({ head, body } = render(BlogIndex, {
|
||||||
|
props: {
|
||||||
|
favicon: websiteData.favicon_image
|
||||||
|
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
|
||||||
|
: "",
|
||||||
|
title: "Legal information",
|
||||||
|
logoType: websiteData.logo_type,
|
||||||
|
logo:
|
||||||
|
websiteData.logo_type === "text"
|
||||||
|
? (websiteData.logo_text ?? "")
|
||||||
|
: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
|
||||||
|
mainContent: md(websiteData.legal_information_main_content ?? "", false),
|
||||||
|
articles: [],
|
||||||
|
footerAdditionalText: md(websiteData.additional_text ?? "")
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "Docs":
|
||||||
|
{
|
||||||
|
({ head, body } = render(DocsIndex, {
|
||||||
|
props: {
|
||||||
|
favicon: websiteData.favicon_image
|
||||||
|
? `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.favicon_image}`
|
||||||
|
: "",
|
||||||
|
title: "Legal information",
|
||||||
|
logoType: websiteData.logo_type,
|
||||||
|
logo:
|
||||||
|
websiteData.logo_type === "text"
|
||||||
|
? (websiteData.logo_text ?? "")
|
||||||
|
: `${API_BASE_PREFIX}/rpc/retrieve_file?id=${websiteData.logo_image}`,
|
||||||
|
mainContent: md(websiteData.legal_information_main_content ?? "", false),
|
||||||
|
categorizedArticles: {},
|
||||||
|
footerAdditionalText: md(websiteData.additional_text ?? "")
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const legalInformationFileContents = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
${head}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${body}
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
|
||||||
|
await writeFile(join(uploadDir, "legal-information.html"), legalInformationFileContents);
|
||||||
|
}
|
||||||
|
|
||||||
const commonStyles = await readFile(`${process.cwd()}/template-styles/common-styles.css`, {
|
const commonStyles = await readFile(`${process.cwd()}/template-styles/common-styles.css`, {
|
||||||
encoding: "utf-8"
|
encoding: "utf-8"
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
{#if data.website.is_published}
|
{#if data.website.is_published}
|
||||||
<section>
|
<section id="publication-status">
|
||||||
<h3>
|
<h3>
|
||||||
<a href="#publication-status">Publication status</a>
|
<a href="#publication-status">Publication status</a>
|
||||||
</h3>
|
</h3>
|
||||||
|
|||||||
@@ -97,6 +97,11 @@ test.describe.serial("Collaborator tests", () => {
|
|||||||
await page.getByLabel("Username:").click();
|
await page.getByLabel("Username:").click();
|
||||||
await page.getByLabel("Username:").fill(collabUsername);
|
await page.getByLabel("Username:").fill(collabUsername);
|
||||||
await page.getByRole("button", { name: "Submit" }).click();
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
|
await page.getByRole("link", { name: "Legal information" }).click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").fill("## Content");
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const permissionLevel of permissionLevels) {
|
for (const permissionLevel of permissionLevels) {
|
||||||
@@ -322,6 +327,45 @@ test.describe.serial("Collaborator tests", () => {
|
|||||||
await expect(page.getByText("You do not have the required")).toBeVisible();
|
await expect(page.getByText("You do not have the required")).toBeVisible();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
test("Create/Update legal information", async ({ page }) => {
|
||||||
|
await page.getByRole("link", { name: "Blog" }).click();
|
||||||
|
await page.getByRole("link", { name: "Legal information" }).click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").fill("## Content");
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
|
if (permissionLevel === 30) {
|
||||||
|
await expect(page.getByText("Successfully created legal")).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(page.getByText("You do not have the required")).toBeVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").fill("## Content updated");
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
|
||||||
|
if (permissionLevel === 30) {
|
||||||
|
await expect(page.getByText("Successfully updated legal")).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(page.getByText("You do not have the required")).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test("Delete legal information", async ({ page }) => {
|
||||||
|
await page
|
||||||
|
.getByRole("link", {
|
||||||
|
name: [10, 20].includes(permissionLevel) ? "Documentation" : "Blog"
|
||||||
|
})
|
||||||
|
.click();
|
||||||
|
await page.getByRole("link", { name: "Legal information" }).click();
|
||||||
|
await page.getByRole("button", { name: "Delete" }).click();
|
||||||
|
await page.getByRole("button", { name: "Delete legal information" }).click();
|
||||||
|
|
||||||
|
if (permissionLevel === 30) {
|
||||||
|
await expect(page.getByText("Successfully deleted legal")).toBeVisible();
|
||||||
|
} else {
|
||||||
|
await expect(page.getByText("You do not have the required")).toBeVisible();
|
||||||
|
}
|
||||||
|
});
|
||||||
test("Create category", async ({ page }) => {
|
test("Create category", async ({ page }) => {
|
||||||
await page.getByRole("link", { name: "Documentation" }).click();
|
await page.getByRole("link", { name: "Documentation" }).click();
|
||||||
await page.getByRole("link", { name: "Categories" }).click();
|
await page.getByRole("link", { name: "Categories" }).click();
|
||||||
|
|||||||
@@ -230,6 +230,29 @@ test.describe.serial("Website tests", () => {
|
|||||||
await expect(page.getByText("Successfully removed")).toBeVisible();
|
await expect(page.getByText("Successfully removed")).toBeVisible();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.describe.serial("Legal information", () => {
|
||||||
|
test("Create/Update legal information", async ({ authenticatedPage: page }) => {
|
||||||
|
await page.getByRole("link", { name: "Blog" }).click();
|
||||||
|
await page.getByRole("link", { name: "Legal information" }).click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").fill("## Content");
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
await expect(page.getByText("Successfully created legal")).toBeVisible();
|
||||||
|
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").click();
|
||||||
|
await page.getByPlaceholder("## Impressum\n\n## Privacy policy").fill("## Content updated");
|
||||||
|
await page.getByRole("button", { name: "Submit" }).click();
|
||||||
|
await expect(page.getByText("Successfully updated legal")).toBeVisible();
|
||||||
|
});
|
||||||
|
test("Delete legal information", async ({ authenticatedPage: page }) => {
|
||||||
|
await page.getByRole("link", { name: "Blog" }).click();
|
||||||
|
await page.getByRole("link", { name: "Legal information" }).click();
|
||||||
|
await page.getByRole("button", { name: "Delete" }).click();
|
||||||
|
await page.getByRole("button", { name: "Delete legal information" }).click();
|
||||||
|
await expect(page.getByText("Successfully deleted legal")).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test.describe.serial("Docs", () => {
|
test.describe.serial("Docs", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user