Skip to content

Commit

Permalink
feat: Add params validate
Browse files Browse the repository at this point in the history
  • Loading branch information
journey-ad committed Oct 20, 2024
1 parent cc1881c commit 77cac2c
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 88 deletions.
2 changes: 1 addition & 1 deletion Readme.md → README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ Glitch can use `.env` file, [documentation](https://help.glitch.com/hc/en-us/art

## License

[MIT License](./LICENSE)
[MIT License](./LICENSE), excluding all themes
Binary file added assets/img/back-to-top.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
63 changes: 63 additions & 0 deletions assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,69 @@ h2, h3, h4, h5 {
margin-bottom: .6em;
}

.back-to-top {
position: fixed;
z-index: 2;
right: -108px;
bottom: 0;
width: 108px;
height: 150px;
background: url('./img/back-to-top.png?v=1') no-repeat 0 0; /* artwork from https://www.pixiv.net/artworks/83996495 */
background-size: 108px 450px;
opacity: 0.6;
transition: opacity 0.3s, right 0.8s;
cursor: pointer;
}
.back-to-top:hover {
background-position: 0 -150px;
opacity: 1;
}
.back-to-top.load {
right: 0;
}
.back-to-top.ani-leave {
background-position: 0 -150px;
animation: ani-leave 390ms ease-in-out;
animation-fill-mode: forwards;
}
.back-to-top.leaved {
pointer-events: none;
background: none;
transition: none;
}
.back-to-top.ending {
pointer-events: none;
}
.back-to-top.ending::after {
opacity: 1;
transition-delay: 0.35s;
}
.back-to-top::after {
content: '';
position: fixed;
z-index: 2;
right: 0;
bottom: 0;
width: 108px;
height: 150px;
background: url('./img/back-to-top.png?v=1') no-repeat 0 0;
background-size: 108px 450px;
background-position: 0 -300px;
transition: opacity 0.3s;
opacity: 0;
pointer-events: none;
}

@keyframes ani-leave {
0% {
transform: translateX(0);
}
100% {
transform: translateX(108px);
}
}


@media screen and (max-width: 900px) {
iframe {
display: none;
Expand Down
96 changes: 54 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
const config = require("config-yml");
const express = require("express");
const compression = require("compression");
const { z } = require("zod");

const db = require("./db");
const themify = require("./utils/themify");
const { themeList, getCountImage } = require("./utils/themify");
const { ZodValid } = require("./utils/zod");
const { randomArray } = require("./utils");

const app = express();

Expand All @@ -15,56 +18,65 @@ app.set("view engine", "pug");

app.get('/', (req, res) => {
const site = config.app.site || `${req.protocol}://${req.get('host')}`
res.render('index', { site })
res.render('index', {
site,
themeList,
})
});

// get the image
app.get(["/@:name", "/get/@:name"], async (req, res) => {
const { name } = req.params;
const { theme = "moebooru", padding = 7, pixelated = '1', darkmode = 'auto' } = req.query;
const isPixelated = pixelated === '1';
app.get(["/@:name", "/get/@:name"],
ZodValid({
params: z.object({
name: z.string().max(32),
}),
query: z.object({
theme: z.string().default("moebooru"),
padding: z.coerce.number().min(0).max(32).default(7),
offset: z.coerce.number().min(-500).max(500).default(0),
scale: z.coerce.number().min(0.1).max(2).default(1),
pixelated: z.enum(["0", "1"]).default("1"),
darkmode: z.enum(["0", "1", "auto"]).default("auto")
})
}),
async (req, res) => {
const { name } = req.params;
let { theme = "moebooru", ...rest } = req.query;

// This helps with GitHub's image cache
res.set({
"content-type": "image/svg+xml",
"cache-control": "max-age=0, no-cache, no-store, must-revalidate",
});

if (name.length > 32) {
res.status(400).send("name too long");
return;
}
const data = await getCountByName(name);

if (padding > 32) {
res.status(400).send("padding too long");
return;
}
if (name === "demo") {
res.set("cache-control", "max-age=31536000");
}

// This helps with GitHub's image cache
res.set({
"content-type": "image/svg+xml",
"cache-control": "max-age=0, no-cache, no-store, must-revalidate",
});
if (theme === "random") {
theme = randomArray(Object.keys(themeList));
}

const data = await getCountByName(name);
// Send the generated SVG as the result
const renderSvg = getCountImage({
count: data.num,
theme,
...rest
});

if (name === "demo") {
res.set("cache-control", "max-age=31536000");
}
res.send(renderSvg);

// Send the generated SVG as the result
const renderSvg = themify.getCountImage({
count: data.num,
theme,
padding,
darkmode,
pixelated: isPixelated
});

res.send(renderSvg);

console.log(
data,
`theme: ${theme}`,
`ip: ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`,
`ref: ${req.get("Referrer") || null}`,
`ua: ${req.get("User-Agent") || null}`
);
});
console.log(
data,
`theme: ${theme}`,
`ip: ${req.headers['x-forwarded-for'] || req.connection.remoteAddress}`,
`ref: ${req.get("Referrer") || null}`,
`ua: ${req.get("User-Agent") || null}`
);
}
);

// JSON record
app.get("/record/@:name", async (req, res) => {
Expand Down
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
"image-size": "^0.8.3",
"mime-types": "^2.1.27",
"mongoose": "^5.9.28",
"pug": "^3.0.0"
"pug": "^3.0.0",
"zod": "^3.23.8"
},
"engines": {
"node": "16.x"
}
}
7 changes: 7 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions utils/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
randomArray: (arr) => {
return arr[Math.floor(Math.random() * arr.length)]
},
}
25 changes: 18 additions & 7 deletions utils/themify.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ fs.readdirSync(themePath).forEach(theme => {
const imgList = fs.readdirSync(path.resolve(themePath, theme))
imgList.forEach(img => {
const imgPath = path.resolve(themePath, theme, img)
const name = path.parse(img).name
const num = path.parse(img).name
const { width, height } = sizeOf(imgPath)

themeList[theme][name] = {
themeList[theme][num] = {
width,
height,
data: convertToDatauri(imgPath)
Expand All @@ -32,8 +32,12 @@ function convertToDatauri(path) {
return `data:${mime};base64,${base64}`
}

function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = true, darkmode = 'auto' }) {
function getCountImage(params) {
let { count, theme = 'moebooru', padding = 7, offset = 0, scale = 1, pixelated = '1', darkmode = 'auto' } = params

if (!(theme in themeList)) theme = 'moebooru'
padding = parseInt(padding, 10)
offset = parseInt(offset, 10)

// This is not the greatest way for generating an SVG but it'll do for now
const countArray = count.toString().padStart(padding, '0').split('')
Expand All @@ -42,7 +46,9 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru
let x = 0, y = 0

const defs = uniqueChar.reduce((ret, cur) => {
const { width, height, data } = themeList[theme][cur]
let { width, height, data } = themeList[theme][cur]
width *= scale
height *= scale

if (height > y) y = height

Expand All @@ -53,19 +59,23 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru
}, '')

const parts = countArray.reduce((ret, cur) => {
const { width } = themeList[theme][cur]
let { width } = themeList[theme][cur]
width *= scale

const image = `${ret}
<use x="${x}" xlink:href="#${cur}" />`

x += width
x += width + offset

return image
}, '')

// Fix the last image offset
x -= offset

const style = `
svg {
${pixelated ? 'image-rendering: pixelated;' : ''}
${pixelated === '1' ? 'image-rendering: pixelated;' : ''}
${darkmode === '1' ? 'filter: brightness(.6);' : ''}
}
${darkmode === 'auto' ? `@media (prefers-color-scheme: dark) { svg { filter: brightness(.6); } }` : ''}
Expand All @@ -85,5 +95,6 @@ function getCountImage({ count, theme = 'moebooru', padding = 7, pixelated = tru
}

module.exports = {
themeList,
getCountImage
}
50 changes: 50 additions & 0 deletions utils/zod.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
function parseError(error) {
const err = JSON.parse(error)[0];

return {
code: 400,
message: `The field \`${err.path[0]}\` is invalid. ${err.message}`,
}
}

module.exports = {
ZodValid: ({ headers, params, query, body }) => {
const handler = (req, res, next) => {
if (headers) {
const result = headers.safeParse(req.headers);
if (!result.success) {
res.status(400).send(parseError(result.error));
return;
}
}

if (params) {
const result = params.safeParse(req.params);
if (!result.success) {
res.status(400).send(parseError(result.error));
return;
}
}

if (query) {
const result = query.safeParse(req.query);
if (!result.success) {
res.status(400).send(parseError(result.error));
return;
}
}

if (body) {
const result = body.safeParse(req.body);
if (!result.success) {
res.status(400).send(parseError(result.error));
return;
}
}

next();
}

return handler
}
}
Loading

0 comments on commit 77cac2c

Please sign in to comment.