Content Manager: Added Documentation, Implemented functional approach to the HTML table generation and refactored API codes, Using DOM purifier to prevent XSS
This commit is contained in:
@@ -1,28 +1,46 @@
|
||||
/** @module api/gallery-image */
|
||||
import express from 'express';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { asyncDatabaseRead, asyncDatabaseWrite } from '../utils/asyncDatabase.js';
|
||||
import { wrapInTable } from '../utils/tableWrapper.js';
|
||||
|
||||
const galleryImageAPI = express.Router();
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const databasePath = path.join(__dirname, "../../assets/databases/gallery.db");
|
||||
|
||||
galleryImageAPI.get('/list', async (request, response) => {
|
||||
function generateActionButtons(target) {
|
||||
return `<button class='delete-button' hx-delete='/api/gallery-image?target=${target}'>Delete</button><a href='/update-gallery-image.html?target=${target}'><button class='edit-button' hx-confirm='unset'>Edit</button></a>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets list of gallery image entries in HTML table body tr+td
|
||||
* @param {Object} request
|
||||
* @param {Object} response
|
||||
* @returns {string} HTML table body tr+td
|
||||
* @example
|
||||
* $ curl -X GET http://localhost:3001/api/gallery-image/list
|
||||
* // <tr>
|
||||
* // <td>1</td>
|
||||
* // <td>/image/test.png</td>
|
||||
* // ...
|
||||
* // </tr>
|
||||
* // ...
|
||||
*/
|
||||
const getGalleryImageList = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
const sqlQuery = "SELECT * FROM gallery";
|
||||
|
||||
try {
|
||||
const result = await asyncDatabaseRead(database, sqlQuery, (rows) => {
|
||||
let ret = "";
|
||||
for (const entry of rows) {
|
||||
ret = ret + "<tr>\n";
|
||||
for (const data in entry) {
|
||||
ret = ret + `\t<td>${entry[data]}</td>\n`;
|
||||
}
|
||||
ret = ret + `\t<td>\n\t\t<button class='delete-button' hx-delete='/api/gallery-image?target=${entry["id"]}'>Delete</button>\n\t\t<a href='/update-gallery-image.html?target=${entry["id"]}'><button class='edit-button' hx-confirm='unset'>Edit</button></a>\n\t</td>\n</tr>\n`;
|
||||
}
|
||||
let rowsCopy = [...rows];
|
||||
const withActionButtons = rowsCopy.map((entry) => {
|
||||
return { ...entry, buttons: generateActionButtons(entry.id) };
|
||||
});
|
||||
ret = wrapInTable(withActionButtons);
|
||||
return ret;
|
||||
});
|
||||
|
||||
@@ -33,9 +51,20 @@ galleryImageAPI.get('/list', async (request, response) => {
|
||||
database.close();
|
||||
response.status(500).send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
galleryImageAPI.get('/list',getGalleryImageList);
|
||||
|
||||
galleryImageAPI.get('/list-unwrapped', async (request, response) => {
|
||||
/**
|
||||
* Gets list of gallery image entries in Unformatted JSON string
|
||||
* @param {Object} request
|
||||
* @param {Object} response
|
||||
* @returns {string} Unformatted JSON string that contains gallery image entries
|
||||
* @example
|
||||
* $ curl -X GET \
|
||||
* http://localhost:3001/api/gallery-image/list-unwrapped
|
||||
* // gets raw JSON containing gallery image entries
|
||||
*/
|
||||
const getGalleryImageListUnwrapped = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
const sqlQuery = "SELECT * FROM gallery";
|
||||
|
||||
@@ -49,9 +78,20 @@ galleryImageAPI.get('/list-unwrapped', async (request, response) => {
|
||||
database.close();
|
||||
response.status(500).send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
galleryImageAPI.get('/list-unwrapped', getGalleryImageListUnwrapped);
|
||||
|
||||
galleryImageAPI.get('/', async (request, response) => {
|
||||
/**
|
||||
* Get a gallery image information
|
||||
* @param {Object} request
|
||||
* @param {number} request.query.target - ID to specify gallery image entry
|
||||
* @param {Object} response
|
||||
* @returns {JSON} JSON that contains information about gallery image
|
||||
* @example
|
||||
* $ curl -X GET http://localhost:3001/api/gallery-image?target=1
|
||||
* // gets gallery image information with ID of 1
|
||||
*/
|
||||
const getGalleryImage = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.query.target);
|
||||
@@ -72,9 +112,24 @@ galleryImageAPI.get('/', async (request, response) => {
|
||||
database.close();
|
||||
response.status(500).send(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
galleryImageAPI.get('/', getGalleryImage);
|
||||
|
||||
galleryImageAPI.post('/', async (request, response) => {
|
||||
/**
|
||||
* Posts gallery image
|
||||
* @param {Object} request
|
||||
* @param {string} request.body.imagePath - URL path to image
|
||||
* @param {string} request.body.caption - Caption of image
|
||||
* @param {Object} response
|
||||
* @returns Result is logged into console
|
||||
* @example
|
||||
* $ curl -X POST \
|
||||
* -H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
* --data-raw 'imagePath=/images/launch.png&caption=Launch of our new rocket' \
|
||||
* http://localhost:3001/api/gallery-image/
|
||||
* // Posts gallery image with given information
|
||||
*/
|
||||
const postGalleryImage = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const imagePath = request.body.imagePath;
|
||||
@@ -93,9 +148,25 @@ galleryImageAPI.post('/', async (request, response) => {
|
||||
|
||||
database.close();
|
||||
response.end();
|
||||
});
|
||||
};
|
||||
galleryImageAPI.post('/', postGalleryImage);
|
||||
|
||||
galleryImageAPI.put('/', async (request, response) => {
|
||||
/**
|
||||
* Updates gallery image
|
||||
* @param {Object} request
|
||||
* @param {number} request.body.target - ID to specify gallery image entry
|
||||
* @param {string} request.body.imagePath - URL path to image
|
||||
* @param {string} request.body.caption - Caption of image
|
||||
* @param {Object} response
|
||||
* @returns Result is logged into console
|
||||
* @example
|
||||
* $ curl -X PUT \
|
||||
* -H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
* --data-raw 'imagePath=/images/launch.png&caption=Launch of our new rocket' \
|
||||
* http://localhost:3001/api/gallery-image/
|
||||
* // Updates gallery image with given information
|
||||
*/
|
||||
const putGalleryImage = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.body.target);
|
||||
@@ -121,9 +192,21 @@ galleryImageAPI.put('/', async (request, response) => {
|
||||
|
||||
database.close();
|
||||
response.end();
|
||||
});
|
||||
};
|
||||
galleryImageAPI.put('/', putGalleryImage);
|
||||
|
||||
galleryImageAPI.delete('/', async (request, response) => {
|
||||
/**
|
||||
* Deletes specified gallery image
|
||||
* @param {Object} request
|
||||
* @param {string} request.query.target - ID to specify gallery image entry
|
||||
* @param {Object} response
|
||||
* @returns Result is logged into console
|
||||
* @example
|
||||
* $ curl -X DELETE \
|
||||
* http://localhost:3001/api/gallery-image?target=1
|
||||
* // Deletes gallery image with ID of 1
|
||||
*/
|
||||
const deleteGalleryImage = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.query.target);
|
||||
@@ -146,6 +229,7 @@ galleryImageAPI.delete('/', async (request, response) => {
|
||||
|
||||
database.close();
|
||||
response.status(200).send();
|
||||
});
|
||||
};
|
||||
galleryImageAPI.delete('/', deleteGalleryImage);
|
||||
|
||||
export default galleryImageAPI;
|
||||
|
||||
@@ -1,15 +1,43 @@
|
||||
/** @module api/news */
|
||||
import express from 'express';
|
||||
import sqlite3 from 'sqlite3';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { asyncDatabaseRead, asyncDatabaseWrite } from '../utils/asyncDatabase.js';
|
||||
import { wrapInTable } from '../utils/tableWrapper.js';
|
||||
|
||||
const newsAPI = express.Router();
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const databasePath = path.join(__dirname, "../../assets/databases/news.db");
|
||||
|
||||
newsAPI.get('/', async (request, response) => {
|
||||
function renameArticleType(entryType) {
|
||||
return entryType == 0 ? "Article" : "Tweet";
|
||||
}
|
||||
|
||||
function unixTimeToHumanreadableTime(unixTime) {
|
||||
return (new Date(unixTime).toLocaleString());
|
||||
}
|
||||
|
||||
function generateActionButtons(target) {
|
||||
return `<button class='delete-button' hx-delete='/api/news?target=${target}'>Delete</button><a href='/update-news.html?target=${target}'><button class='edit-button' hx-confirm='unset'>Edit</button></a>`;
|
||||
}
|
||||
|
||||
function modifyOrAppendProperty(targetObject, key, value) {
|
||||
return { ...targetObject, [key]: value };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get single news entry specified by Un*x Timestamp
|
||||
* @param {Object} request
|
||||
* @param {number} request.query.target - Un*x Timestamp to specify news entry
|
||||
* @param {Object} response
|
||||
* @returns {JSON} news entry data
|
||||
* @example
|
||||
* $ curl -X GET http://localhost:3001/api/news?target=0
|
||||
* // gets news posted on Un*x epoch in JSON format
|
||||
*/
|
||||
const getNewsEntry = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.query.target);
|
||||
@@ -23,16 +51,34 @@ newsAPI.get('/', async (request, response) => {
|
||||
const sqlQuery = `SELECT * FROM news WHERE date = ${target}`;
|
||||
|
||||
try {
|
||||
const result = await asyncDatabaseRead(database, sqlQuery, (rows) => {return rows[0]});
|
||||
const result = await asyncDatabaseRead(database, sqlQuery, (rows) => { return rows[0] });
|
||||
database.close();
|
||||
response.send(result);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
database.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
newsAPI.get('/', getNewsEntry);
|
||||
|
||||
newsAPI.post('/', async (request, response) => {
|
||||
/**
|
||||
* Post news and write to database
|
||||
* @param {Object} request
|
||||
* @param {number} request.body.entryType - Number that represents type of news (0: article, 1: tweet)
|
||||
* @param {string} request.body.cardContent - Content of news card, Markdown is allowed
|
||||
* @param {string} request.body.article - Article written in Markdown
|
||||
* @param {string} request.body.linkPath - Relative URL path to the article
|
||||
* @param {string} request.body.coverImagePath - Relative URL path to the cover image
|
||||
* @param {Object} response
|
||||
* @returns result is logged into console
|
||||
* @example
|
||||
* $ curl -X POST \
|
||||
* -H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
* --data-raw 'entryType=1&cardContent=Test&article=&linkPath=&coverImagePath=/default.png' \
|
||||
* http://localhost:3001/api/news
|
||||
* // Posts Tweet style news with content "Test" and cover image "/default.png"
|
||||
*/
|
||||
const postNewsEntry = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const currentDate = new Date();
|
||||
@@ -55,9 +101,28 @@ newsAPI.post('/', async (request, response) => {
|
||||
}
|
||||
database.close();
|
||||
response.end();
|
||||
});
|
||||
};
|
||||
newsAPI.post('/', postNewsEntry);
|
||||
|
||||
newsAPI.put('/', async (request, response) => {
|
||||
/**
|
||||
* Update news entry
|
||||
* @param {Object} request
|
||||
* @param {number} request.body.target - Un*x timestamp to identify the entry
|
||||
* @param {number} request.body.entryType - Number that represents type of news (0: article, 1: tweet)
|
||||
* @param {string} request.body.cardContent - Content of news card, Markdown is allowed
|
||||
* @param {string} request.body.article - Article written in Markdown
|
||||
* @param {string} request.body.linkPath - Relative URL path to the article
|
||||
* @param {string} request.body.coverImagePath - Relative URL path to the cover image
|
||||
* @param {Object} response
|
||||
* @returns result is logged into console
|
||||
* @example
|
||||
* $ curl -X PUT \
|
||||
* -H 'Content-Type: application/x-www-form-urlencoded' \
|
||||
* --data-raw 'target=0&entryType=1&cardContent=Test&article=&linkPath=&coverImagePath=default.png' \
|
||||
* http://localhost:3001/api/news
|
||||
* // Update news posted on Un*x Epoch with given contents
|
||||
*/
|
||||
const putNewsEntry = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.body.target);
|
||||
@@ -86,9 +151,21 @@ newsAPI.put('/', async (request, response) => {
|
||||
|
||||
database.close();
|
||||
response.end();
|
||||
});
|
||||
};
|
||||
newsAPI.put('/', putNewsEntry);
|
||||
|
||||
newsAPI.delete('/', async (request, response) => {
|
||||
/**
|
||||
* Delete news specified by Un*x Timestamp
|
||||
* @param {Object} request
|
||||
* @param {number} request.query.target - Un*x timestamp to identify the entry
|
||||
* @param {Object} response
|
||||
* @returns result is logged into console
|
||||
* @example
|
||||
* $ curl -X DELETE \
|
||||
* http://localhost:3001/api/news?target=0
|
||||
* // Delete news posted on Un*x Epoch
|
||||
*/
|
||||
const deleteNewsEntry = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
|
||||
const target = Number(request.query.target);
|
||||
@@ -113,30 +190,45 @@ newsAPI.delete('/', async (request, response) => {
|
||||
database.close();
|
||||
|
||||
response.status(200).send();
|
||||
});
|
||||
};
|
||||
newsAPI.delete('/', deleteNewsEntry);
|
||||
|
||||
newsAPI.get('/list', async (request, response) => {
|
||||
/**
|
||||
* Get news list in HTML table body tr+td
|
||||
* @param {Object} request
|
||||
* @param {Object} response
|
||||
* @returns {string} HTML table body tr+td
|
||||
* @example
|
||||
* $ curl -X GET \
|
||||
* http://localhost:3001/api/news/list
|
||||
* // <tr>
|
||||
* // <td>1</td>
|
||||
* // <td>1970/1/1 0:0:0</td>
|
||||
* // ...
|
||||
* // </tr>
|
||||
* // ...
|
||||
*/
|
||||
const getNewsList = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
const sqlQuery = `SELECT id, date, entryType, cardContent FROM news ORDER BY date DESC;`;
|
||||
try {
|
||||
const result = await asyncDatabaseRead(database, sqlQuery, (rows) => {
|
||||
let ret = "";
|
||||
for (const entry of rows) {
|
||||
ret = ret + "<tr>\n";
|
||||
for (const data in entry) {
|
||||
if (data == "entryType") {
|
||||
ret = ret + `\t<td>${entry[data] == 0 ? "Article" : "Tweet"}</td>\n`;
|
||||
continue;
|
||||
}
|
||||
if (data == "date") {
|
||||
ret = ret + `\t<td>${new Date(entry[data]).toLocaleString()}</td>\n`;
|
||||
continue;
|
||||
}
|
||||
ret = ret + `\t<td>${entry[data]}</td>\n`;
|
||||
}
|
||||
ret = ret + `\t<td>\n\t\t<button class='delete-button' hx-delete='/api/news?target=${entry["date"]}'>Delete</button>\n\t\t<a href='/update-news.html?target=${entry["date"]}'><button class='edit-button' hx-confirm='unset'>Edit</button></a>\n\t</td>\n</tr>\n`;
|
||||
}
|
||||
return ret
|
||||
const rowsCopy = [...rows];
|
||||
const rowsWithButtons = rowsCopy.map((entry) => {
|
||||
const appendButtons = (value) => modifyOrAppendProperty(entry, "buttons", value);
|
||||
return appendButtons(generateActionButtons(entry.date));
|
||||
});
|
||||
const renamedArticleTypeRows = rowsWithButtons.map((entry) => {
|
||||
const modifyEntryType = (value) => modifyOrAppendProperty(entry, "entryType", value);
|
||||
return modifyEntryType(renameArticleType(entry.entryType));
|
||||
});
|
||||
const convertedTimeRows = renamedArticleTypeRows.map((entry) => {
|
||||
const modifyDate = (value) => modifyOrAppendProperty(entry, "date", value);
|
||||
return modifyDate(unixTimeToHumanreadableTime(entry.date));
|
||||
});
|
||||
ret = wrapInTable(convertedTimeRows);
|
||||
return ret;
|
||||
});
|
||||
database.close();
|
||||
response.send(result);
|
||||
@@ -144,9 +236,20 @@ newsAPI.get('/list', async (request, response) => {
|
||||
console.error(err);
|
||||
database.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
newsAPI.get('/list', getNewsList);
|
||||
|
||||
newsAPI.get('/list-unwrapped', async (request, response) => {
|
||||
/**
|
||||
* Get news list in unformated raw JSON string
|
||||
* @param {Object} request
|
||||
* @param {Object} response
|
||||
* @returns {JSON} Unformatted JSON string that contains news entries
|
||||
* @example
|
||||
* $ curl -X GET \
|
||||
* http://localhost:3001/api/news/list-unwrapped
|
||||
* // gets raw JSON containing news entries
|
||||
*/
|
||||
const getNewsListUnrwapped = async (request, response) => {
|
||||
const database = new sqlite3.Database(databasePath);
|
||||
const sqlQuery = `SELECT id, date, entryType, cardContent FROM news ORDER BY date DESC;`;
|
||||
|
||||
@@ -159,6 +262,7 @@ newsAPI.get('/list-unwrapped', async (request, response) => {
|
||||
console.error(err);
|
||||
database.close();
|
||||
}
|
||||
});
|
||||
};
|
||||
newsAPI.get('/list-unwrapped', getNewsListUnrwapped);
|
||||
|
||||
export default newsAPI;
|
||||
|
||||
Reference in New Issue
Block a user