Update backend for editing communications pages

This commit is contained in:
Miika Holmlund
2025-11-12 13:40:40 +02:00
parent 4298cc8c12
commit 8425f59938
6 changed files with 50 additions and 44 deletions
+1 -2
View File
@@ -51,7 +51,6 @@ services:
command: >
sh -c "apt update && apt install -y wget postgresql-client &&
wget https://github.com/peterldowns/pgmigrate/releases/download/v0.0.6%2Bcommit.4f90829/pgmigrate-linux-amd64 -O /usr/local/bin/pgmigrate &&
chmod +x /usr/local/bin/pgmigrate &&
/usr/local/bin/docker-entrypoint.sh sleep inf"
chmod +x /usr/local/bin/pgmigrate && sleep inf"
volumes:
pg-data:
@@ -18,26 +18,26 @@ import { useTestDatabase } from "@/backend/test/testdb";
import { useTestRequest } from "@/backend/test/mock-request";
import { getHTMLContent } from "@/backend/html-services";
describe("GET /api/html-content/[id]", () => {
describe("GET /api/html-content/[type]/[id]", () => {
useTestDatabase();
beforeEach(() => {
vi.clearAllMocks();
});
const getRequest = async (id, type = "resources") => {
const getRequest = async (type, id) => {
const { mockGet } = useTestRequest();
const req = mockGet(`/api/html-content/${id}?type=${type}`);
return GET(req);
const req = mockGet(`/api/html-content/${type}/${id}`);
return GET(req, { params: { type, id } });
};
it("should return HTML content as JSON", async () => {
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
const response = await getRequest(1, "resources");
const response = await getRequest("resources", 1);
const body = await response.json();
expect(getHTMLContent).toHaveBeenCalledWith("1", "learning_pages_html");
expect(getHTMLContent).toHaveBeenCalledWith("learning_pages_html", 1);
expect(response.status).toBe(200);
expect(body).toEqual({ content: "<p>Mocked HTML</p>" });
});
@@ -45,21 +45,18 @@ describe("GET /api/html-content/[id]", () => {
it("should call correct table for communications type", async () => {
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
const response = await getRequest(5, "communications");
const response = await getRequest("communications", 5);
await response.json();
expect(getHTMLContent).toHaveBeenCalledWith(
"5",
"communications_pages_html"
);
expect(getHTMLContent).toHaveBeenCalledWith("communications_pages_html", 5);
});
it("should return empty string table if type is invalid", async () => {
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
const response = await getRequest(3, "unknown");
const response = await getRequest("unknown", 3);
await response.json();
expect(getHTMLContent).toHaveBeenCalledWith("3", "");
expect(getHTMLContent).toHaveBeenCalledWith("", 3);
});
});
@@ -6,19 +6,8 @@ import DOMPurify from "dompurify";
/*
This route fetches html content from db according to id and page type
*/
export async function GET(req) {
// create URL-object from request
const url = new URL(req.url);
// 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];
export async function GET(req, { params }) {
const { type, id } = await params;
// returns correct db table based on url. Prevents malicious code
const getTableType = (type) => {
if (type == "resources") {
@@ -30,7 +19,7 @@ export async function GET(req) {
}
};
const content = await getHTMLContent(id, getTableType(type));
const content = await getHTMLContent(getTableType(type), id);
// returns the fetched html as json
return new Response(JSON.stringify({ content }), {
@@ -41,13 +30,23 @@ export async function GET(req) {
export const PUT = withAuth(
async (req, { params }) => {
const data = await req.json();
const { id } = await params;
const { type, id } = await params;
const getTableType = (type) => {
if (type == "resources") {
return "learning_pages_html";
} else if (type == "communications") {
return "communications_pages_html";
} else {
return "";
}
};
const window = new JSDOM("").window;
const purify = DOMPurify(window);
const cleaned = purify.sanitize(data.content, { ADD_ATTR: ["target"] });
if (!(await updateHTMLContent(id, cleaned))) {
if (!(await updateHTMLContent(getTableType(type), id, cleaned))) {
return new Response("Error updating HTML content", {
status: 400,
});
+1 -1
View File
@@ -22,7 +22,7 @@ export default function RenderHTML({ contentId, type }) {
// 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}?type=${type}`);
const res = await fetch(`/api/html-content/${type}/${contentId}`);
// chnages the response to proper json
const data = await res.json();
+12 -9
View File
@@ -22,7 +22,7 @@ export default function Chapters() {
useEffect(() => {
async function fetchHTML() {
const res = await fetch(
`/api/html-content/${parseInt(chapter)}?type=resources`
`/api/html-content/resources/${parseInt(chapter)}`
);
const data = await res.json();
setEditorContent(data.content);
@@ -35,14 +35,17 @@ export default function Chapters() {
const jsonData = JSON.stringify({
content: editorContent.replace(/&nbsp;/g, " "),
});
const res = await fetch(`/api/html-content/${parseInt(chapter)}`, {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "PUT",
body: jsonData,
});
const res = await fetch(
`/api/html-content/resources/${parseInt(chapter)}`,
{
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
method: "PUT",
body: jsonData,
}
);
if (res.status == 200) {
setEditorMessage({ error: false, msg: "Updated successfully" });
} else {
+11 -3
View File
@@ -1,5 +1,7 @@
import { DB } from "@/backend/db";
const allowedTables = ["learning_pages_html", "communications_pages_html"];
/*
This function fetches html content from database
@@ -15,9 +17,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, type) {
export async function getHTMLContent(type, id) {
try {
console.log("Table type: ", type);
if (!allowedTables.includes(type)) {
return "<p>Error loading page</p>";
}
// get content from db using parameterized query
const result = await DB.pool(
"SELECT content FROM " + type + " WHERE id = $1",
@@ -49,10 +54,13 @@ NOTE: This function also might need to be updated if the functions in these fi
should be used to fetch and update any html content, and not just the chapter pages.
In this case, same kind of modifications apply as seen above in function getHTMLContent(id)
*/
export async function updateHTMLContent(id, content) {
export async function updateHTMLContent(type, id, content) {
if (!allowedTables.includes(type)) {
return false;
}
try {
const result = await DB.pool(
"UPDATE learning_pages_html SET content = $1 WHERE id = $2",
"UPDATE " + type + " SET content = $1 WHERE id = $2",
[content, id]
);
if (result.rowCount == 1) {