Skip to content

Commit

Permalink
Add feature to hide fields from rendered output in certificates, refa…
Browse files Browse the repository at this point in the history
…ctor validation code, improve schema and controllers
  • Loading branch information
paramsiddharth committed Jul 16, 2021
1 parent d382ada commit 5716684
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 34 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,7 @@ dist

# Temporary files
tmp/
temp/
temp/

# Static content
/static
32 changes: 25 additions & 7 deletions src/controllers/certificate.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ const {
imgToBase64
} = require('../helpers/image');
const {
convertTo
convertTo,
defaultValue: getDefaultValue
} = require('../helpers/types');
const {
render
Expand All @@ -30,7 +31,7 @@ const {
const validate = async body => {
const { values } = body;

if (values.filter(v => v?.value == null).length > 0)
if (values.filter(v => v?.value == null && v?.visible !== false).length > 0)
throw new Error(`Empty values not allowed!`);

const template = await Template.findOne({ name: body.template });
Expand All @@ -40,15 +41,12 @@ const validate = async body => {
const validValues = [];

for (const field of template.fields) {
if (field.placeholder)
continue;

const {
name,
type,
defaultValue,
required,
fixed
fixed,
} = field;

const matches = values.filter(v => v.name === name);
Expand All @@ -57,6 +55,22 @@ const validate = async body => {

const newField = matches[0];

let visible = true;
if ('visible' in (newField ?? {}))
visible = newField.visible;

if (newField?.visible === false)
newField.value = getDefaultValue[type];

if (field.placeholder) {
if (visible !== false)
continue;

newField.value = field.value;
validValues.push({ ...newField });
continue;
}

// Check if required and missing
if (
(newField == null || newField.value == null)
Expand Down Expand Up @@ -113,7 +127,8 @@ const validate = async body => {

validValues.push({
name,
value: newValue
value: newValue,
visible
});
}

Expand Down Expand Up @@ -339,6 +354,9 @@ const renderCertificate = async (req, res) => {

const v = certificate.values.filter(v => v.name === f.name)[0];
f.value = v.value;

if (!v.visible)
f.skip = true;
}

try {
Expand Down
63 changes: 40 additions & 23 deletions src/controllers/template.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,66 +20,67 @@ const {
} = require('../helpers/placeholder');

/**
* Validate and do something
* @param {Request} req
* @param {Response} res
* Get the valid version
* @param {Object} body
*/
const validateAndDoSomething = async (req, res, body) => {
const validate = async body => {
if (!await validateImage(body.background))
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for certificate background: Image not found!`);
throw new Error(`Invalid value for certificate background: Image not found!`);

const imgLocation = await getImageLocation(body.background);
if (!imgLocation)
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for background: Image not accessible!`);
throw new Error(`Invalid value for background: Image not accessible!`);

body.background = imgLocation;

if (body.fields == null)
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for fields!`);
throw new Error(`Invalid value for fields!`);

for (const field of body.fields) {
if (body.fields.filter(f => f.name === field.name).length > 1)
return res.status(statusCode.BAD_REQUEST).send(`Duplicate fields named '${field.name}' received!`);
throw new Error(`Duplicate fields named '${field.name}' received!`);

if (field.type == null)
field.type = 'String';

if (['Number', 'Boolean', 'String', 'Image', 'Date'].indexOf(field.type) < 0)
return res.status(statusCode.BAD_REQUEST).send(`Invalid type for field '${field.name}': Only Number, Boolean, String, Image, and Date allowed.`);
throw new Error(`Invalid type for field '${field.name}': Only Number, Boolean, String, Image, and Date allowed.`);

if (['TITLE', 'template', 'uid', '_id'].indexOf(field.name) >= 0)
return res.status(statusCode.NOT_ACCEPTABLE).send(`Invalid name for field '${field.name}': Name not allowed for fields.`);
throw new Error(`Invalid name for field '${field.name}': Name not allowed for fields.`);

if ((field.fixed || field.placeholder) && field.value == null)
return res.status(statusCode.BAD_REQUEST).send(`Fixed field '${field.name}' cannot have an empty value.`);
throw new Error(`Fixed field '${field.name}' cannot have an empty value.`);

if (field.placeholder)
if (!isValidPlaceholder(field))
return res.status(statusCode.BAD_REQUEST).send(`Field '${field.name}' is an invalid placeholder!`);
throw new Error(`Field '${field.name}' is an invalid placeholder!`);
else
continue;

if (field.type === 'Image') {
if (field.image == null)
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': An expected size must be defined.`);
throw new Error(`Invalid value for field '${field.name}': An expected size must be defined.`);

const { value, defaultValue } = field ?? {};
if (value != null && !await validateImage(value))
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Image not found!`);
throw new Error(`Invalid value for field '${field.name}': Image not found!`);

if (value != null) {
const imgLocation = await getImageLocation(value);
if (!imgLocation)
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Image not accessible!`);
throw new Error(`Invalid value for field '${field.name}': Image not accessible!`);

field.value = imgLocation;
}

if (defaultValue != null && !await validateImage(defaultValue))
return res.status(statusCode.BAD_REQUEST).send(`Invalid default value for field '${field.name}': Image not found!`);
throw new Error(`Invalid default value for field '${field.name}': Image not found!`);

if (defaultValue != null) {
const imgLocation = await getImageLocation(defaultValue);
if (!imgLocation)
return res.status(statusCode.BAD_REQUEST).send(`Invalid default value for field '${field.name}': Image not accessible!`);
throw new Error(`Invalid default value for field '${field.name}': Image not accessible!`);

field.defaultValue = imgLocation;
}
Expand All @@ -93,7 +94,7 @@ const validateAndDoSomething = async (req, res, body) => {
} = field.textFormat;

if (style != null && style?.type == 'gradient' && (style.gradient == null || style.gradient.stops?.length < 2))
return res.status(statusCode.BAD_REQUEST).send(`Invalid style for field '${field.name}': Invalid gradient configuration!`);
throw new Error(`Invalid style for field '${field.name}': Invalid gradient configuration!`);
}

if (field.type === 'Date') {
Expand All @@ -106,7 +107,7 @@ const validateAndDoSomething = async (req, res, body) => {
try {
new Date(Date.parse(value)).toISOString();
} catch(e) {
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
throw new Error(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
}
}
}
Expand All @@ -118,14 +119,28 @@ const validateAndDoSomething = async (req, res, body) => {
try {
new Date(Date.parse(defaultValue)).toISOString();
} catch(e) {
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
throw new Error(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
}
}
}
}
}
};

return true;
/**
* Validate and do something
* @param {Request} req
* @param {Response} res
* @param {Object} body
*/
const validateAndDoSomething = async (req, res, body) => {
try {
await validate(body);
return true;
} catch(e) {
res.status(statusCode.BAD_REQUEST).send(e.message);
return false;
}
};

/**
Expand Down Expand Up @@ -512,6 +527,7 @@ const patch = async (req, res) => {
*/
const exportTemplate = async (req, res) => {
const { name } = req.params;
const plain = 'plain' in req.query;
const template = await Template.findOne({ name });

if (template == null)
Expand All @@ -527,7 +543,8 @@ const exportTemplate = async (req, res) => {
if (exportedObj[prop]) delete exportedObj[prop];

try {
exportedObj.background = await imgToBase64(exportedObj.background);
if (!plain)
exportedObj.background = await imgToBase64(exportedObj.background);
} catch(e) {
const msg = `Failed to export template '${name}': Unable to export background (${e.message})!`;
console.error(msg);
Expand All @@ -536,7 +553,7 @@ const exportTemplate = async (req, res) => {
}

for (const field of exportedObj.fields) {
if (field.type !== 'Image')
if (field.type !== 'Image' || plain)
continue;

try {
Expand Down
15 changes: 14 additions & 1 deletion src/helpers/render.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const fs = require('fs-extra');
const path = require('path');
const { createCanvas, loadImage } = require('canvas');
const { createCanvas, loadImage, registerFont } = require('canvas');
const strftime = require('strftime');

const {
Expand All @@ -11,6 +12,18 @@ const {
SINGLE_WHITE_PIXEL
} = require('../constants');

const RESOURCES = path.join(INTERNAL_STATIC_DIR, 'fonts.json');
if (fs.existsSync(RESOURCES)) {
const fonts = fs.readJSONSync(RESOURCES).filter(i => i.type === 'font');
for (const font of fonts) {
const {
path: fontPath,
family
} = font;
registerFont(path.join(INTERNAL_STATIC_DIR, fontPath), { family });
}
}

// Render a preview of a template or a certificate
const render = async (cert, fmt) => {
let {
Expand Down
15 changes: 14 additions & 1 deletion src/helpers/types.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
const {
SINGLE_WHITE_PIXEL
} = require('../constants');

const convertTo = {
Number: v => {
if (v == null || v === '')
Expand Down Expand Up @@ -45,6 +49,15 @@ const convertTo = {
}
};

const defaultValue = {
Number: 0,
get Date() { return new Date(Date.now()); },
String: 'Hello',
Image: SINGLE_WHITE_PIXEL,
Boolean: true
};

module.exports = {
convertTo
convertTo,
defaultValue
};
9 changes: 8 additions & 1 deletion src/models/schemes.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,10 @@ const fieldSchema = new Schema({
required: false,
default: null
},
position: xySchema,
position: {
type: xySchema,
required: true
},
fixed: {
type: Boolean,
default: false
Expand All @@ -194,6 +197,10 @@ const valueSchema = new Schema({
value: {
type: Mixed,
default: null
},
visible: {
type: Boolean,
default: true
}
}, {
_id: false
Expand Down

0 comments on commit 5716684

Please sign in to comment.