Skip to content

Commit 546e120

Browse files
committed
backend(Card.ts): add a CardBuilder class with the support of zod and use it in the card-controller
1 parent 90537b8 commit 546e120

5 files changed

Lines changed: 143 additions & 113 deletions

File tree

package-lock.json

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"dependencies": {
2323
"cors": "^2.8.5",
2424
"cross-fetch": "^3.1.5",
25-
"express": "^4.18.2"
25+
"express": "^4.18.2",
26+
"zod": "^3.20.2"
2627
},
2728
"devDependencies": {
2829
"@types/cors": "^2.8.13",

src/cards/card-builder.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { z } from "zod";
2+
import Card from "./card";
3+
import { getThemeByName } from "./themes";
4+
import { Badge } from "./types";
5+
6+
const title = z.string();
7+
const lineCount = z.number().min(1).catch(1);
8+
const align = z.enum(["left", "center", "right"]).catch("left");
9+
const showBorder = z.boolean().catch(true);
10+
const borderRadius = z.number().min(0).max(50).catch(4.5);
11+
const fontWeight = z
12+
.enum(["thin", "normal", "semibold", "bold"])
13+
.catch("semibold");
14+
const fontSize = z.number().min(15).max(30).catch(18);
15+
16+
// const numberThatAcceptsString = z.preprocess((input) => {
17+
// const processed = z
18+
// .string()
19+
// .regex(/^\d+$/)
20+
// .transform(Number)
21+
// .safeParse(input);
22+
// return processed.success ? processed.data : input;
23+
// }, z.number().min(15).max(30).catch(18));
24+
25+
export default class CardBuilder {
26+
private card: Card;
27+
28+
public constructor() {
29+
this.card = new Card();
30+
}
31+
32+
private reset(): void {
33+
this.card = new Card();
34+
}
35+
36+
public build(): Card {
37+
const result = this.card;
38+
this.reset();
39+
return result;
40+
}
41+
42+
public title(_title = "My Tech Stack"): CardBuilder {
43+
this.card.setTitle(title.parse(_title));
44+
return this;
45+
}
46+
47+
public lineCount(_lineCount = "1"): CardBuilder {
48+
this.card.setLineCount(lineCount.parse(Number(_lineCount)));
49+
return this;
50+
}
51+
52+
public align(_align = "left"): CardBuilder {
53+
this.card.setBadgeAlign(align.parse(_align));
54+
return this;
55+
}
56+
57+
public border(_showBorder = "true"): CardBuilder {
58+
this.card.setShowBorder(
59+
showBorder.parse(_showBorder === "false" ? false : true)
60+
);
61+
return this;
62+
}
63+
64+
public borderRadius(_borderRadius = "4.5"): CardBuilder {
65+
this.card.setBorderRadius(borderRadius.parse(Number(_borderRadius)));
66+
return this;
67+
}
68+
69+
public fontWeight(_fontWeight = "semibold"): CardBuilder {
70+
this.card.setFontWeight(fontWeight.parse(_fontWeight));
71+
return this;
72+
}
73+
74+
public fontSize(_fontSize = "18"): CardBuilder {
75+
this.card.setFontSize(fontSize.parse(Number(_fontSize)));
76+
return this;
77+
}
78+
79+
public theme(_theme = "github"): CardBuilder {
80+
this.card.setTheme(getThemeByName(_theme));
81+
return this;
82+
}
83+
84+
public lines(
85+
cb: (line: number, addBadge: (b: Badge) => void) => void
86+
): CardBuilder {
87+
// for loop from 1 to the line count
88+
// each iteration calls the callback function
89+
for (let i = 1; i <= this.card.getLineCount(); i++) {
90+
cb(i, (b) => this.card.addBadge(i, b));
91+
}
92+
93+
return this;
94+
}
95+
}

src/controllers/cards-controller.ts

Lines changed: 30 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,38 @@
11
import { Request, Response } from "express";
2-
import Card from "../cards/card";
3-
import { getThemeByName } from "../cards/themes";
2+
import CardBuilder from "../cards/card-builder";
43
import SvgGenerator from "../svg/svg-generator";
5-
import {
6-
validateAlign,
7-
validateBorderRadius,
8-
validateFontSize,
9-
validateLine,
10-
validateLineCount,
11-
} from "../utils/validator";
4+
import { validateLine } from "../utils/validator";
125

136
export const getCard = async (req: Request, res: Response) => {
14-
const card = new Card();
7+
const {
8+
title,
9+
lineCount,
10+
align,
11+
showBorder,
12+
borderRadius,
13+
fontWeight,
14+
fontSize,
15+
theme,
16+
} = req.query;
1517

16-
const title = req.query.title?.toString() || "My Tech Stack";
17-
const theme = req.query.theme?.toString() || "";
18-
const lineCount = req.query.lineCount?.toString() || "1";
19-
const align = req.query.align?.toString() || "left";
20-
const showBorder = req.query.showBorder?.toString() || "true";
21-
const borderRadius = req.query.borderRadius?.toString() || "4.5";
22-
const fontWeight = req.query.fontWeight?.toString() || "semibold";
23-
const fontSize = req.query.fontSize?.toString() || "18";
24-
25-
card.setTitle(title);
26-
card.setTheme(getThemeByName(theme));
27-
card.setLineCount(validateLineCount(lineCount));
28-
card.setBadgeAlign(validateAlign(align));
29-
card.setShowBorder(showBorder !== "false");
30-
card.setBorderRadius(validateBorderRadius(borderRadius));
31-
card.setFontWeight(fontWeight);
32-
card.setFontSize(validateFontSize(fontSize));
33-
34-
// run a loop card.getLineCount() times
35-
for (let i = 1; i <= card.getLineCount(); i++) {
36-
// get the dynamic query param (line + i)
37-
const lineValue = req.query[`line${i}`]?.toString() || "";
38-
39-
[...validateLine(lineValue)].forEach((b) => card.addBadge(i, b));
40-
}
18+
const card = new CardBuilder()
19+
.title(title?.toString())
20+
.lineCount(lineCount?.toString())
21+
.align(align?.toString())
22+
.border(showBorder?.toString())
23+
.borderRadius(borderRadius?.toString())
24+
.fontWeight(fontWeight?.toString())
25+
.fontSize(fontSize?.toString())
26+
.theme(theme?.toString())
27+
.lines((line, addBadge) => {
28+
// get the line query param based on the `line` argument (example: line1)
29+
// validate the line
30+
// iterate through it, then append every badge
31+
validateLine(req.query[`line${line}`]?.toString() || "").forEach((b) =>
32+
addBadge(b)
33+
);
34+
})
35+
.build();
4136

4237
res.setHeader("Content-Type", "image/svg+xml");
4338
res.send(await new SvgGenerator(card).toString());

src/utils/validator.ts

Lines changed: 1 addition & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,6 @@
1-
import { Badge, BadgeAlign } from "../cards/types";
1+
import { Badge } from "../cards/types";
22
import { formatHexColor } from "./hex-color";
33

4-
/**
5-
* Validates the line.
6-
* If the line is not valid, it returns 1.
7-
* The line is invalid when it's not a number or less than one.
8-
*
9-
* @param {number} lineCount The number of lines.
10-
* @returns {number} A valid lineCount.
11-
*/
12-
export const validateLineCount = (lineCount: string): number => {
13-
const lineNum = parseInt(lineCount);
14-
15-
// it's not a number
16-
if (isNaN(lineNum)) {
17-
return 1;
18-
}
19-
20-
// it's less than one
21-
return lineNum < 1 ? 1 : lineNum;
22-
};
23-
24-
/**
25-
* Validates the border-radius.
26-
* If the border-radius is not valid, it returns 4.5.
27-
* The border-radius is invalid when it's not a number, less than 0 or greater than 50.
28-
*
29-
* @param {number} borderRadius The raw border radius
30-
* @returns {number} A valid borderRadius.
31-
*/
32-
export const validateBorderRadius = (lineCount: string): number => {
33-
const num = parseInt(lineCount);
34-
35-
// it's not a number
36-
if (isNaN(num)) {
37-
return 4.5;
38-
}
39-
40-
return num > 50 || num < 0 ? 4.5 : num;
41-
};
42-
43-
/**
44-
* Validates the font size.
45-
*
46-
* @param {number} fontSize The raw fontSize.
47-
* @returns {number} A valid fontSize.
48-
*/
49-
export const validateFontSize = (fontSize: string): number => {
50-
const num = parseInt(fontSize);
51-
52-
// it's not a number
53-
if (isNaN(num)) {
54-
return 18;
55-
}
56-
57-
return num > 30 || num < 15 ? 18 : num;
58-
};
59-
60-
/**
61-
* Validates the given align.
62-
*
63-
* @param {string} align The alignment
64-
* @returns {BadgeAlign} A valid BadgeAlign
65-
*/
66-
export const validateAlign = (align: string): BadgeAlign => {
67-
switch (align) {
68-
case "left":
69-
return "left";
70-
case "center" || "middle":
71-
return "center";
72-
case "right":
73-
return "right";
74-
default:
75-
return "left";
76-
}
77-
};
78-
794
/**
805
* Converts the line into a Badge array.
816
* If there's any error in the line,

0 commit comments

Comments
 (0)