Skip to content

Commit 5716684

Browse files
Add feature to hide fields from rendered output in certificates, refactor validation code, improve schema and controllers
1 parent d382ada commit 5716684

6 files changed

Lines changed: 105 additions & 34 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,4 +129,7 @@ dist
129129

130130
# Temporary files
131131
tmp/
132-
temp/
132+
temp/
133+
134+
# Static content
135+
/static

src/controllers/certificate.js

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ const {
1515
imgToBase64
1616
} = require('../helpers/image');
1717
const {
18-
convertTo
18+
convertTo,
19+
defaultValue: getDefaultValue
1920
} = require('../helpers/types');
2021
const {
2122
render
@@ -30,7 +31,7 @@ const {
3031
const validate = async body => {
3132
const { values } = body;
3233

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

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

4243
for (const field of template.fields) {
43-
if (field.placeholder)
44-
continue;
45-
4644
const {
4745
name,
4846
type,
4947
defaultValue,
5048
required,
51-
fixed
49+
fixed,
5250
} = field;
5351

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

5856
const newField = matches[0];
5957

58+
let visible = true;
59+
if ('visible' in (newField ?? {}))
60+
visible = newField.visible;
61+
62+
if (newField?.visible === false)
63+
newField.value = getDefaultValue[type];
64+
65+
if (field.placeholder) {
66+
if (visible !== false)
67+
continue;
68+
69+
newField.value = field.value;
70+
validValues.push({ ...newField });
71+
continue;
72+
}
73+
6074
// Check if required and missing
6175
if (
6276
(newField == null || newField.value == null)
@@ -113,7 +127,8 @@ const validate = async body => {
113127

114128
validValues.push({
115129
name,
116-
value: newValue
130+
value: newValue,
131+
visible
117132
});
118133
}
119134

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

340355
const v = certificate.values.filter(v => v.name === f.name)[0];
341356
f.value = v.value;
357+
358+
if (!v.visible)
359+
f.skip = true;
342360
}
343361

344362
try {

src/controllers/template.js

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -20,66 +20,67 @@ const {
2020
} = require('../helpers/placeholder');
2121

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

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

3634
body.background = imgLocation;
3735

3836
if (body.fields == null)
39-
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for fields!`);
37+
throw new Error(`Invalid value for fields!`);
4038

4139
for (const field of body.fields) {
4240
if (body.fields.filter(f => f.name === field.name).length > 1)
43-
return res.status(statusCode.BAD_REQUEST).send(`Duplicate fields named '${field.name}' received!`);
41+
throw new Error(`Duplicate fields named '${field.name}' received!`);
42+
43+
if (field.type == null)
44+
field.type = 'String';
4445

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

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

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

5455
if (field.placeholder)
5556
if (!isValidPlaceholder(field))
56-
return res.status(statusCode.BAD_REQUEST).send(`Field '${field.name}' is an invalid placeholder!`);
57+
throw new Error(`Field '${field.name}' is an invalid placeholder!`);
5758
else
5859
continue;
5960

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

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

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

7374
field.value = imgLocation;
7475
}
7576

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

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

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

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

99100
if (field.type === 'Date') {
@@ -106,7 +107,7 @@ const validateAndDoSomething = async (req, res, body) => {
106107
try {
107108
new Date(Date.parse(value)).toISOString();
108109
} catch(e) {
109-
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
110+
throw new Error(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
110111
}
111112
}
112113
}
@@ -118,14 +119,28 @@ const validateAndDoSomething = async (req, res, body) => {
118119
try {
119120
new Date(Date.parse(defaultValue)).toISOString();
120121
} catch(e) {
121-
return res.status(statusCode.BAD_REQUEST).send(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
122+
throw new Error(`Invalid value for field '${field.name}': Invalid date! Use the UTC/ISO format.`);
122123
}
123124
}
124125
}
125126
}
126127
}
128+
};
127129

128-
return true;
130+
/**
131+
* Validate and do something
132+
* @param {Request} req
133+
* @param {Response} res
134+
* @param {Object} body
135+
*/
136+
const validateAndDoSomething = async (req, res, body) => {
137+
try {
138+
await validate(body);
139+
return true;
140+
} catch(e) {
141+
res.status(statusCode.BAD_REQUEST).send(e.message);
142+
return false;
143+
}
129144
};
130145

131146
/**
@@ -512,6 +527,7 @@ const patch = async (req, res) => {
512527
*/
513528
const exportTemplate = async (req, res) => {
514529
const { name } = req.params;
530+
const plain = 'plain' in req.query;
515531
const template = await Template.findOne({ name });
516532

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

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

538555
for (const field of exportedObj.fields) {
539-
if (field.type !== 'Image')
556+
if (field.type !== 'Image' || plain)
540557
continue;
541558

542559
try {

src/helpers/render.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
const fs = require('fs-extra');
12
const path = require('path');
2-
const { createCanvas, loadImage } = require('canvas');
3+
const { createCanvas, loadImage, registerFont } = require('canvas');
34
const strftime = require('strftime');
45

56
const {
@@ -11,6 +12,18 @@ const {
1112
SINGLE_WHITE_PIXEL
1213
} = require('../constants');
1314

15+
const RESOURCES = path.join(INTERNAL_STATIC_DIR, 'fonts.json');
16+
if (fs.existsSync(RESOURCES)) {
17+
const fonts = fs.readJSONSync(RESOURCES).filter(i => i.type === 'font');
18+
for (const font of fonts) {
19+
const {
20+
path: fontPath,
21+
family
22+
} = font;
23+
registerFont(path.join(INTERNAL_STATIC_DIR, fontPath), { family });
24+
}
25+
}
26+
1427
// Render a preview of a template or a certificate
1528
const render = async (cert, fmt) => {
1629
let {

src/helpers/types.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
const {
2+
SINGLE_WHITE_PIXEL
3+
} = require('../constants');
4+
15
const convertTo = {
26
Number: v => {
37
if (v == null || v === '')
@@ -45,6 +49,15 @@ const convertTo = {
4549
}
4650
};
4751

52+
const defaultValue = {
53+
Number: 0,
54+
get Date() { return new Date(Date.now()); },
55+
String: 'Hello',
56+
Image: SINGLE_WHITE_PIXEL,
57+
Boolean: true
58+
};
59+
4860
module.exports = {
49-
convertTo
61+
convertTo,
62+
defaultValue
5063
};

src/models/schemes.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,10 @@ const fieldSchema = new Schema({
177177
required: false,
178178
default: null
179179
},
180-
position: xySchema,
180+
position: {
181+
type: xySchema,
182+
required: true
183+
},
181184
fixed: {
182185
type: Boolean,
183186
default: false
@@ -194,6 +197,10 @@ const valueSchema = new Schema({
194197
value: {
195198
type: Mixed,
196199
default: null
200+
},
201+
visible: {
202+
type: Boolean,
203+
default: true
197204
}
198205
}, {
199206
_id: false

0 commit comments

Comments
 (0)