first working app

This commit is contained in:
2025-07-08 13:33:46 +02:00
parent 5ed6bcc33b
commit ef97c4b752
4 changed files with 255 additions and 88 deletions
+36 -1
View File
@@ -1,3 +1,38 @@
# badgedex # badgedex
BadgeDex BadgeDex
# BadgeDex API URL Paraméterek
Az alábbi paraméterekkel szabályozhatod a `/badge` végpont működését:
| Paraméter | Alapértelmezett érték | Leírás | Példa értékek |
|-------------|-----------------------|----------------------------------------------------------------------------------------|------------------------------|
| `icon` | `portainer` | A SimpleIcons ikon neve (kisbetű, kötőjellel). | `github`, `docker`, `nginx` |
| `label` | `Portainer` | A badge-en megjelenő szöveg. | `MyApp`, `Status` |
| `bg` | `#f0f0f0` | Háttérszín (hex vagy CSS színnév). | `#fff`, `#222`, `red` |
| `color` | `#000000` | Szöveg színe (hex vagy CSS színnév). | `#333`, `white` |
| `size` | `24` | Ikon (és badge) magassága pixelben. | `16`, `32`, `64` |
| `labelpos` | `right` | Szöveg pozíciója az ikonhoz képest: `right`, `left`, `above`, `below`. | `left`, `above` |
| `fontweight`| `normal` | Szöveg vastagsága (`normal`, `bold`, `lighter`, szám is lehet pl. `600`). | `bold`, `600` |
## Példák
- **Alap badge:**
`/badge`
- **GitHub ikon, piros háttér, fehér szöveg:**
`/badge?icon=github&bg=red&color=white`
- **Szöveg balra, ikon jobbra:**
`/badge?labelpos=left`
- **Szöveg az ikon fölött, félkövér betű:**
`/badge?labelpos=above&fontweight=bold`
- **Nagyobb ikon és szöveg:**
`/badge?size=48`
---
A badge SVG formátumban tér vissza, így közvetlenül beilleszthető HTML-be vagy Markdown
+92
View File
@@ -0,0 +1,92 @@
const express = require('express');
const axios = require('axios');
const app = express();
const PORT = process.env.PORT || 3000;
app.get('/badge', async (req, res) => {
const {
icon = 'portainer',
label = 'Portainer',
bg = '#f0f0f0',
color = '#000000',
size = 24,
labelpos = 'right', // lehet: 'right', 'left', 'above', 'below'
fontweight = 'normal', // új paraméter, alapértelmezett: normal
} = req.query;
try {
// Ikon letöltése
const iconUrl = `https://cdn.simpleicons.org/${icon}?viewbox=auto&size=${size}`;
const iconResponse = await axios.get(iconUrl, { responseType: 'text' });
if (iconResponse.status !== 200) {
return res.status(404).send('Icon not found');
}
const iconSVG = iconResponse.data;
// Badge méretek
const padding = 10;
const iconSize = parseInt(size);
const fontSize = Math.round(iconSize * 0.6); // Font size az ikon méret 60%-a
const textPadding = Math.round(iconSize * 0.4); // Dinamikus: size 40%-a
const radius = 8;
const approxCharWidth = fontSize * 0.6;
const textWidth = label.length * approxCharWidth;
let width, height, iconGroup, textElem;
// Szebb, élsimított font shields.io stílusban
const fontFamily = "Verdana,Geneva,DejaVu Sans,sans-serif";
const fontWeight = fontweight; // paraméterből
if (labelpos === 'above' || labelpos === 'below') {
width = Math.max(iconSize, textWidth) + padding * 2;
height = padding * 2 + iconSize + textPadding + fontSize;
if (labelpos === 'above') {
// Szöveg felül, ikon alul
textElem = `<text x="${width / 2}" y="${padding + fontSize}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${color}" text-anchor="middle" dominant-baseline="middle">${label}</text>`;
iconGroup = `<g transform="translate(${(width - iconSize) / 2}, ${padding + fontSize + textPadding})">${iconSVG}</g>`;
} else {
// Szöveg alul, ikon felül
iconGroup = `<g transform="translate(${(width - iconSize) / 2}, ${padding})">${iconSVG}</g>`;
textElem = `<text x="${width / 2}" y="${padding + iconSize + textPadding}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${color}" text-anchor="middle" dominant-baseline="hanging">${label}</text>`;
}
} else if (labelpos === 'left') {
width = padding * 2 + iconSize + textPadding + textWidth;
height = Math.max(iconSize + padding * 2, fontSize + padding * 2);
// Szöveg balra, ikon jobbra (függőleges közép)
textElem = `<text x="${padding}" y="${height / 2}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${color}" alignment-baseline="middle" dominant-baseline="middle">${label}</text>`;
iconGroup = `<g transform="translate(${padding + textWidth + textPadding}, ${(height - iconSize) / 2})">${iconSVG}</g>`;
} else {
width = padding * 2 + iconSize + textPadding + textWidth;
height = Math.max(iconSize + padding * 2, fontSize + padding * 2);
// Alapértelmezett: ikon balra, szöveg jobbra (függőleges közép)
iconGroup = `<g transform="translate(${padding}, ${(height - iconSize) / 2})">${iconSVG}</g>`;
textElem = `<text x="${padding + iconSize + textPadding + textWidth / 2}" y="${height / 2}" font-size="${fontSize}" font-family="${fontFamily}" font-weight="${fontWeight}" fill="${color}" text-anchor="middle" dominant-baseline="middle">${label}</text>`;
}
// SVG badge string összefűzéssel, szöveg árnyékkal
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" style="background:none">
<defs>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="1" flood-color="#000" flood-opacity="0.25"/>
</filter>
</defs>
<rect x="0" y="0" width="${width}" height="${height}" rx="${radius}" fill="${bg}"/>
${iconGroup}
${textElem.replace('<text ', '<text filter="url(#shadow)" ')}
</svg>
`.trim();
res.setHeader('Content-Type', 'image/svg+xml');
res.send(svg);
} catch (err) {
console.error(err);
res.status(500).send('Internal Server Error');
}
});
app.listen(PORT, () => {
console.log(`🚀 Badge API running at http://localhost:${PORT}/badge`);
});
+126 -86
View File
@@ -10,8 +10,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.js": "^3.2.4",
"axios": "^1.10.0",
"express": "^5.1.0", "express": "^5.1.0",
"node-fetch": "^3.3.2",
"svgdom": "^0.1.21" "svgdom": "^0.1.21"
} }
}, },
@@ -47,6 +47,23 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/axios": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.10.0.tgz",
"integrity": "sha512-/1xYAC4MP/HEG+3duIhFr4ZQXR4sQXOIe+o6sdqzeykGLx6Upp/1p8MHqhINOvGeP7xyNHe7tsiJByc4SSVUxw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -143,6 +160,18 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
@@ -182,15 +211,6 @@
"node": ">=6.6.0" "node": ">=6.6.0"
} }
}, },
"node_modules/data-uri-to-buffer": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
"license": "MIT",
"engines": {
"node": ">= 12"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.1", "version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
@@ -208,6 +228,15 @@
} }
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -282,6 +311,21 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/es-set-tostringtag": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"get-intrinsic": "^1.2.6",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -345,29 +389,6 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/fetch-blob": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
}
},
"node_modules/finalhandler": { "node_modules/finalhandler": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz",
@@ -385,6 +406,26 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/fontkit": { "node_modules/fontkit": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz", "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-2.0.4.tgz",
@@ -402,16 +443,41 @@
"unicode-trie": "^2.0.0" "unicode-trie": "^2.0.0"
} }
}, },
"node_modules/formdata-polyfill": { "node_modules/form-data": {
"version": "4.0.10", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", "integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"fetch-blob": "^3.1.2" "asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
}, },
"engines": { "engines": {
"node": ">=12.20.0" "node": ">= 6"
}
},
"node_modules/form-data/node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/form-data/node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
} }
}, },
"node_modules/forwarded": { "node_modules/forwarded": {
@@ -502,6 +568,21 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -653,44 +734,6 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"deprecated": "Use your platform's native DOMException instead",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
}
],
"license": "MIT",
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
"license": "MIT",
"dependencies": {
"data-uri-to-buffer": "^4.0.0",
"fetch-blob": "^3.1.4",
"formdata-polyfill": "^4.0.10"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -761,6 +804,12 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/qs": { "node_modules/qs": {
"version": "6.14.0", "version": "6.14.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
@@ -1075,15 +1124,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/web-streams-polyfill": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
"license": "MIT",
"engines": {
"node": ">= 8"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+1 -1
View File
@@ -19,8 +19,8 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@svgdotjs/svg.js": "^3.2.4", "@svgdotjs/svg.js": "^3.2.4",
"axios": "^1.10.0",
"express": "^5.1.0", "express": "^5.1.0",
"node-fetch": "^3.3.2",
"svgdom": "^0.1.21" "svgdom": "^0.1.21"
} }
} }