mirror of
https://github.com/OHTU-German-learning-website/OHTU-German-learning-website.git
synced 2026-10-12 19:13:51 +03:00
Update backend for editing communications pages
This commit is contained in:
+1
-2
@@ -51,7 +51,6 @@ services:
|
|||||||
command: >
|
command: >
|
||||||
sh -c "apt update && apt install -y wget postgresql-client &&
|
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 &&
|
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 &&
|
chmod +x /usr/local/bin/pgmigrate && sleep inf"
|
||||||
/usr/local/bin/docker-entrypoint.sh sleep inf"
|
|
||||||
volumes:
|
volumes:
|
||||||
pg-data:
|
pg-data:
|
||||||
|
|||||||
+10
-13
@@ -18,26 +18,26 @@ import { useTestDatabase } from "@/backend/test/testdb";
|
|||||||
import { useTestRequest } from "@/backend/test/mock-request";
|
import { useTestRequest } from "@/backend/test/mock-request";
|
||||||
import { getHTMLContent } from "@/backend/html-services";
|
import { getHTMLContent } from "@/backend/html-services";
|
||||||
|
|
||||||
describe("GET /api/html-content/[id]", () => {
|
describe("GET /api/html-content/[type]/[id]", () => {
|
||||||
useTestDatabase();
|
useTestDatabase();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRequest = async (id, type = "resources") => {
|
const getRequest = async (type, id) => {
|
||||||
const { mockGet } = useTestRequest();
|
const { mockGet } = useTestRequest();
|
||||||
const req = mockGet(`/api/html-content/${id}?type=${type}`);
|
const req = mockGet(`/api/html-content/${type}/${id}`);
|
||||||
return GET(req);
|
return GET(req, { params: { type, id } });
|
||||||
};
|
};
|
||||||
|
|
||||||
it("should return HTML content as JSON", async () => {
|
it("should return HTML content as JSON", async () => {
|
||||||
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
||||||
|
|
||||||
const response = await getRequest(1, "resources");
|
const response = await getRequest("resources", 1);
|
||||||
const body = await response.json();
|
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(response.status).toBe(200);
|
||||||
expect(body).toEqual({ content: "<p>Mocked HTML</p>" });
|
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 () => {
|
it("should call correct table for communications type", async () => {
|
||||||
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
||||||
|
|
||||||
const response = await getRequest(5, "communications");
|
const response = await getRequest("communications", 5);
|
||||||
await response.json();
|
await response.json();
|
||||||
|
|
||||||
expect(getHTMLContent).toHaveBeenCalledWith(
|
expect(getHTMLContent).toHaveBeenCalledWith("communications_pages_html", 5);
|
||||||
"5",
|
|
||||||
"communications_pages_html"
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return empty string table if type is invalid", async () => {
|
it("should return empty string table if type is invalid", async () => {
|
||||||
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
getHTMLContent.mockResolvedValueOnce("<p>Mocked HTML</p>");
|
||||||
|
|
||||||
const response = await getRequest(3, "unknown");
|
const response = await getRequest("unknown", 3);
|
||||||
await response.json();
|
await response.json();
|
||||||
|
|
||||||
expect(getHTMLContent).toHaveBeenCalledWith("3", "");
|
expect(getHTMLContent).toHaveBeenCalledWith("", 3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
+15
-16
@@ -6,19 +6,8 @@ import DOMPurify from "dompurify";
|
|||||||
/*
|
/*
|
||||||
This route fetches html content from db according to id and page type
|
This route fetches html content from db according to id and page type
|
||||||
*/
|
*/
|
||||||
export async function GET(req) {
|
export async function GET(req, { params }) {
|
||||||
// create URL-object from request
|
const { type, id } = await params;
|
||||||
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];
|
|
||||||
|
|
||||||
// returns correct db table based on url. Prevents malicious code
|
// returns correct db table based on url. Prevents malicious code
|
||||||
const getTableType = (type) => {
|
const getTableType = (type) => {
|
||||||
if (type == "resources") {
|
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
|
// returns the fetched html as json
|
||||||
return new Response(JSON.stringify({ content }), {
|
return new Response(JSON.stringify({ content }), {
|
||||||
@@ -41,13 +30,23 @@ export async function GET(req) {
|
|||||||
export const PUT = withAuth(
|
export const PUT = withAuth(
|
||||||
async (req, { params }) => {
|
async (req, { params }) => {
|
||||||
const data = await req.json();
|
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 window = new JSDOM("").window;
|
||||||
const purify = DOMPurify(window);
|
const purify = DOMPurify(window);
|
||||||
const cleaned = purify.sanitize(data.content, { ADD_ATTR: ["target"] });
|
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", {
|
return new Response("Error updating HTML content", {
|
||||||
status: 400,
|
status: 400,
|
||||||
});
|
});
|
||||||
@@ -22,7 +22,7 @@ export default function RenderHTML({ contentId, type }) {
|
|||||||
// function to fetch HTML content using html-services, and set it to state html
|
// function to fetch HTML content using html-services, and set it to state html
|
||||||
async function fetchHTML() {
|
async function fetchHTML() {
|
||||||
// fetch response through next.js route using getHTMLContent function
|
// 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
|
// chnages the response to proper json
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ export default function Chapters() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchHTML() {
|
async function fetchHTML() {
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`/api/html-content/${parseInt(chapter)}?type=resources`
|
`/api/html-content/resources/${parseInt(chapter)}`
|
||||||
);
|
);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setEditorContent(data.content);
|
setEditorContent(data.content);
|
||||||
@@ -35,14 +35,17 @@ export default function Chapters() {
|
|||||||
const jsonData = JSON.stringify({
|
const jsonData = JSON.stringify({
|
||||||
content: editorContent.replace(/ /g, " "),
|
content: editorContent.replace(/ /g, " "),
|
||||||
});
|
});
|
||||||
const res = await fetch(`/api/html-content/${parseInt(chapter)}`, {
|
const res = await fetch(
|
||||||
headers: {
|
`/api/html-content/resources/${parseInt(chapter)}`,
|
||||||
Accept: "application/json",
|
{
|
||||||
"Content-Type": "application/json",
|
headers: {
|
||||||
},
|
Accept: "application/json",
|
||||||
method: "PUT",
|
"Content-Type": "application/json",
|
||||||
body: jsonData,
|
},
|
||||||
});
|
method: "PUT",
|
||||||
|
body: jsonData,
|
||||||
|
}
|
||||||
|
);
|
||||||
if (res.status == 200) {
|
if (res.status == 200) {
|
||||||
setEditorMessage({ error: false, msg: "Updated successfully" });
|
setEditorMessage({ error: false, msg: "Updated successfully" });
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { DB } from "@/backend/db";
|
import { DB } from "@/backend/db";
|
||||||
|
|
||||||
|
const allowedTables = ["learning_pages_html", "communications_pages_html"];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
This function fetches html content from database
|
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.
|
other identifier as parameter for this function.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
export async function getHTMLContent(id, type) {
|
export async function getHTMLContent(type, id) {
|
||||||
try {
|
try {
|
||||||
console.log("Table type: ", type);
|
console.log("Table type: ", type);
|
||||||
|
if (!allowedTables.includes(type)) {
|
||||||
|
return "<p>Error loading page</p>";
|
||||||
|
}
|
||||||
// get content from db using parameterized query
|
// get content from db using parameterized query
|
||||||
const result = await DB.pool(
|
const result = await DB.pool(
|
||||||
"SELECT content FROM " + type + " WHERE id = $1",
|
"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.
|
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)
|
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 {
|
try {
|
||||||
const result = await DB.pool(
|
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]
|
[content, id]
|
||||||
);
|
);
|
||||||
if (result.rowCount == 1) {
|
if (result.rowCount == 1) {
|
||||||
|
|||||||
Reference in New Issue
Block a user