Merge branch '384-migrate-communications-texts-to-db' into 416-fix-conflicts-with-new-code-and-older-tests

This commit is contained in:
Saara Pasonen
2025-11-01 16:42:20 +02:00
9 changed files with 195 additions and 219 deletions
@@ -0,0 +1,5 @@
CREATE TABLE communications_pages_html (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL
);
@@ -1,113 +1,5 @@
import Link from "next/link";
import Image from "next/image";
import { Container, Column } from "@/components/ui/layout/container";
import { GlossaryParagraph } from "@/components/ui/glossary/GlossaryText";
import "../../themes/lessons.css";
export const chapters = [
{
id: "1",
title: "1. Über Vergangenes sprechen",
content: Chapter1,
link: "/grammar/communications/1",
},
{
id: "2",
title: "2. Anleitungen formulieren",
content: Chapter2,
link: "/grammar/communications/2",
},
{
id: "3",
title: "3. Offizielle Mitteilungen (Nachrichten usw.) formulieren",
content: Chapter3,
link: "/grammar/communications/3",
},
{
id: "4",
title:
"4. Höflich mit anderen Menschen umgehen (Sich vorstellen, Gäste empfangen und betreuen, Small Talk)",
content: Chapter4,
link: "/grammar/communications/4",
},
{
id: "5",
title: "5. Menschen beschreiben / Ich und meine Familie",
content: Chapter5,
link: "/grammar/communications/5",
},
{
id: "6",
title: "6. Über Hobbys und Freizeit sprechen",
content: Chapter6,
link: "/grammar/communications/6",
},
{
id: "7",
title: "7. Über Beruf und Arbeitsplatz sprechen",
content: Chapter7,
link: "/grammar/communications/7",
},
{
id: "8",
title: "8. Firmen und Produkte beschreiben",
content: Chapter8,
link: "/grammar/communications/8",
},
{
id: "9",
title: "9. Werbung machen",
content: Chapter9,
link: "/grammar/communications/9",
},
{
id: "10",
title: "10. Einkaufen privat und geschäftlich",
content: Chapter10,
link: "/grammar/communications/10",
},
{
id: "11",
title: "11. Räume beschreiben",
content: Chapter11,
link: "/grammar/communications/11",
},
{
id: "12",
title: "12. Über historische Ereignisse sprechen",
content: Chapter12,
link: "/grammar/communications/12",
},
{
id: "13",
title: "13. Pläne machen",
content: Chapter13,
link: "/grammar/communications/13",
},
{
id: "14",
title:
"14. an Versammlungen und Geschäftstreffen teilnehmen und sie leiten",
content: Chapter14,
link: "/grammar/communications/14",
},
{
id: "15",
title: "15. Jemandem gratulieren",
content: Chapter15,
link: "/grammar/communications/15",
},
{
id: "16",
title: "16. Sich beschweren / etw. reklamieren",
content: Chapter16,
link: "/grammar/communications/16",
},
];
function Chapter1() {
return (
<Container className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (1, '<Container className="chapter-content">
<ul>
<li>
<Link href="/grammar/themes/perfekt">Perfekt</Link>
@@ -201,83 +93,69 @@ function Chapter1() {
width={872}
height={387}
/>
</Container>
);
}
</Container>')
ON CONFLICT (id) DO NOTHING;
function Chapter2() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (2, '<Column className="chapter-content">
<ul>
<li>Passiv</li>
<li>Infinitivkonstruktionen</li>
<li>Finalsätze u. a.</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter3() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (3, '<Column className="chapter-content">
<ul>
<li>Konjunktiv I bei indirekten Zitaten</li>
<li>indirekte Frage- und Aussagesätze</li>
<li>subjektive Modalität</li>
<li>Futur II u. a. Vermutungen</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter4() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (4, '<Column className="chapter-content">
<ul>
<li>Konjunktiv II als Höflichkeitsform</li>
<li>Das Pronomen es (z. B. bei Witterungsverben)</li>
<li>Dativ-Verben (geben, schenken usw.)</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter5() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (5, '<Column className="chapter-content">
<ul>
<li>Vergleiche (wie und als)</li>
<li>Adjektivdeklination</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter6() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (6, '<Column className="chapter-content">
<ul>
<li>Adjektivdeklination</li>
<li>Temporalangaben, -adverbien, -sätze</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter7() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (7, '<Column className="chapter-content">
<ul>
<li>Hilfsverben</li>
<li>Artikelverwendung</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter8() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (8, '<Column className="chapter-content">
<ul>
<li>Adjektivdeklination</li>
<li>Lokalangaben, -adverbien, -sätze</li>
@@ -286,25 +164,21 @@ function Chapter8() {
Adverbien zur Darstellung von Chronologie in Prozessbeschreibungen
</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter9() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (9, '<Column className="chapter-content">
<ul>
<li>Adjektivdeklination</li>
<li>Vergleiche</li>
<li>Präpositionen bei der Beschreibung von Leistungen und Produkten</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter10() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (10, '<Column className="chapter-content">
<ul>
<li>Genuszuordnung</li>
<li>Formen der Artikel</li>
@@ -312,13 +186,11 @@ function Chapter10() {
<li>andere Artikelwörter</li>
<li>Akkusativ Bildung und Verwendung</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter11() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (11, '<Column className="chapter-content">
<ul>
<li>
Lokalpräpositionen (Wechsel- und ausgewählte lokale
@@ -326,49 +198,41 @@ function Chapter11() {
</li>
<li>Lokaladverbien (insb. Verwendung im Satz)</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter12() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (12, '<Column className="chapter-content">
<ul>
<li>Vergangenheitstempora, auch (hyster.) Präsens</li>
<li>Temporaladverbien</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter13() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (13, '<Column className="chapter-content">
<ul>
<li>Präsens + Temporaladverbien</li>
<li>Temporalangaben und -sätze</li>
<li>Konditionalsätze (real/irreal mit Konjunktiv II und würde)</li>
<li>Futur I</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter14() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (14, '<Column className="chapter-content">
<ul>
<li>Temporaladverbien</li>
<li>Versch. Konjunktionalsätze</li>
<li>Uneingeleitete Nebensätze</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter15() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (15, '<Column className="chapter-content">
<ul>
<li>Komparation der Adjektive</li>
<li>Anrede (du/Sie)</li>
@@ -376,18 +240,15 @@ function Chapter15() {
<Link href="/grammar/themes/perfekt">Perfekt</Link> / Präteritum
</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
function Chapter16() {
return (
<Column className="chapter-content">
INSERT INTO communications_pages_html (id, content)
VALUES (16, '<Column className="chapter-content">
<ul>
<li>Konjunktiv</li>
<li>Imperativ</li>
<li>Rektion der Verben</li>
</ul>
</Column>
);
}
</Column>')
ON CONFLICT (id) DO NOTHING;
+9
View File
@@ -119,6 +119,15 @@ ALTER TABLE public.click_to_exercises
ADD CONSTRAINT click_to_exercises_exercise_id_fkey
FOREIGN KEY (exercise_id) REFERENCES exercises(id);
CREATE SEQUENCE public.communications_pages_html_id_seq;
CREATE TABLE public.communications_pages_html (
id integer PRIMARY KEY NOT NULL DEFAULT nextval('communications_pages_html_id_seq'::regclass),
content text NOT NULL
);
ALTER SEQUENCE public.communications_pages_html_id_seq OWNED BY public.communications_pages_html.id;
CREATE TABLE public.dnd_categories (
id bigint PRIMARY KEY NOT NULL GENERATED BY DEFAULT AS IDENTITY,
created_at timestamp with time zone NOT NULL DEFAULT now(),
+16 -3
View File
@@ -4,7 +4,7 @@ import { JSDOM } from "jsdom";
import DOMPurify from "dompurify";
/*
This route fetches html content from db according to id
This route fetches html content from db according to id and page type
*/
export async function GET(req) {
// create URL-object from request
@@ -13,11 +13,24 @@ export async function GET(req) {
// split path to segments
const pathSegments = url.pathname.split("/");
// get page type from url parameters
const type = url.searchParams.get("type");
// get last segment, which is the correct id of the page
const id = pathSegments[pathSegments.length - 1];
// get content from db based on id
const content = await getHTMLContent(id);
// returns correct db table based on url. Prevents malicious code
const getTableType = (type) => {
if (type == "resources") {
return "learning_pages_html";
} else if (type == "communications") {
return "communications_pages_html";
} else {
return "";
}
};
const content = await getHTMLContent(id, getTableType(type));
// returns the fetched html as json
return new Response(JSON.stringify({ content }), {
@@ -5,7 +5,7 @@ import "../../../resources/[chapter]/chapters.css";
import layout from "@/shared/styles/layout.module.css";
import { useParams, useRouter } from "next/navigation";
import { LinkButton } from "@/components/ui/linkbutton";
import { chapters } from "./chapters";
import RenderHTML, { PageType } from "@/app/html-renderer";
export default function Chapters() {
const { chapter } = useParams();
@@ -33,7 +33,7 @@ export default function Chapters() {
erklärt.
</p>
<p>Folgende Grammatik-Themen sind damit verbunden: </p>
<Chapter.content />
<RenderHTML contentId={Chapter.id} type="communications" />
</>
)}
<Row justify="space-between" pb="xl">
@@ -51,3 +51,88 @@ export default function Chapters() {
</Column>
);
}
export const chapters = [
{
id: "1",
title: "1. Über Vergangenes sprechen",
link: "/grammar/communications/1",
},
{
id: "2",
title: "2. Anleitungen formulieren",
link: "/grammar/communications/2",
},
{
id: "3",
title: "3. Offizielle Mitteilungen (Nachrichten usw.) formulieren",
link: "/grammar/communications/3",
},
{
id: "4",
title:
"4. Höflich mit anderen Menschen umgehen (Sich vorstellen, Gäste empfangen und betreuen, Small Talk)",
link: "/grammar/communications/4",
},
{
id: "5",
title: "5. Menschen beschreiben / Ich und meine Familie",
link: "/grammar/communications/5",
},
{
id: "6",
title: "6. Über Hobbys und Freizeit sprechen",
link: "/grammar/communications/6",
},
{
id: "7",
title: "7. Über Beruf und Arbeitsplatz sprechen",
link: "/grammar/communications/7",
},
{
id: "8",
title: "8. Firmen und Produkte beschreiben",
link: "/grammar/communications/8",
},
{
id: "9",
title: "9. Werbung machen",
link: "/grammar/communications/9",
},
{
id: "10",
title: "10. Einkaufen privat und geschäftlich",
link: "/grammar/communications/10",
},
{
id: "11",
title: "11. Räume beschreiben",
link: "/grammar/communications/11",
},
{
id: "12",
title: "12. Über historische Ereignisse sprechen",
link: "/grammar/communications/12",
},
{
id: "13",
title: "13. Pläne machen",
link: "/grammar/communications/13",
},
{
id: "14",
title:
"14. an Versammlungen und Geschäftstreffen teilnehmen und sie leiten",
link: "/grammar/communications/14",
},
{
id: "15",
title: "15. Jemandem gratulieren",
link: "/grammar/communications/15",
},
{
id: "16",
title: "16. Sich beschweren / etw. reklamieren",
link: "/grammar/communications/16",
},
];
@@ -1,6 +1,6 @@
"use client";
import { chapters } from "./[chapter]/chapters";
import { chapters } from "./[chapter]/page";
import Link from "next/link";
import "@/app/grammar/themes/lessons.css";
+5 -6
View File
@@ -6,15 +6,14 @@ The returned content can be used by js functions that are used to render individ
parameter contentId: id of the HTML content in db
NOTE: Currently this function fetches content corresponding to id. This is ok when rendering
lesson pages, since they are named 1-n. This function could be used to render any page content,
if the fetching function was changed in backend to find content based on some other identifier.
This function probably doens't need any changes, but at this moment it is only used to render chapter pages.
parameter type: page type
NOTE: This function could be enhanced by providing HTML sanitizing before returning content.
One option for this would be using dompurify sanitizer
*/
export default function RenderHTML({ contentId }) {
export default function RenderHTML({ contentId, type }) {
// create state for HTML content
const [html, setHtml] = useState("");
@@ -23,7 +22,7 @@ export default function RenderHTML({ contentId }) {
// function to fetch HTML content using html-services, and set it to state html
async function fetchHTML() {
// fetch response through next.js route using getHTMLContent function
const res = await fetch(`/api/html-content/${contentId}`);
const res = await fetch(`/api/html-content/${contentId}?type=${type}`);
// chnages the response to proper json
const data = await res.json();
+3 -2
View File
@@ -8,8 +8,9 @@ import { useParams, useRouter } from "next/navigation";
import { LinkButton } from "@/components/ui/linkbutton";
import { Button } from "@/components/ui/button";
import Editor from "@/components/ui/editor";
import RenderHTML from "@/app/html-renderer";
import RenderHTML, { PageType } from "@/app/html-renderer";
import { checkUseIsAdmin } from "@/context/user.context";
import { PAGE_TYPES } from "next/dist/lib/page-types";
export default function Chapters() {
const { chapter } = useParams();
@@ -91,7 +92,7 @@ export default function Chapters() {
{checkUseIsAdmin() == true && (
<Button onClick={() => setEditorActive(true)}>Open editor</Button>
)}
<RenderHTML contentId={Chapter.id} />
<RenderHTML contentId={Chapter.id} type="resources" />
{/*<Chapter.content />*/}
</>
)}
+5 -2
View File
@@ -5,6 +5,8 @@ This function fetches html content from database
parameter id: id of the content in table learning_pages_html
parameter type: PageType enum denoting the sql table name
NOTE: This function could be used to fetch html content to all pages, not just
for chapter pages, e.g. gradesa/resources/*. In that case, the database table
should be updated to include some kind of identifier other than id number
@@ -13,11 +15,12 @@ NOTE: This function could be used to fetch html content to all pages, not just
other identifier as parameter for this function.
*/
export async function getHTMLContent(id) {
export async function getHTMLContent(id, type) {
try {
console.log("Table type: ", type);
// get content from db using parameterized query
const result = await DB.pool(
"SELECT content FROM learning_pages_html WHERE id = $1",
"SELECT content FROM " + type + " WHERE id = $1",
[id]
);