The great refactor (#82)

This commit is contained in:
Simon Lecoq
2021-01-30 12:31:09 +01:00
committed by GitHub
parent f8c6d19a4e
commit 682e43e10b
158 changed files with 6738 additions and 5022 deletions

View File

@@ -6,9 +6,9 @@
import compression from "compression"
import cache from "memory-cache"
import util from "util"
import setup from "../setup.mjs"
import mocks from "../mocks.mjs"
import metrics from "../metrics.mjs"
import setup from "../metrics/setup.mjs"
import mocks from "../mocks/index.mjs"
import metrics from "../metrics/index.mjs"
/** App */
export default async function ({mock, nosettings} = {}) {
@@ -66,8 +66,11 @@
//Base routes
const limiter = ratelimit({max:debug ? Number.MAX_SAFE_INTEGER : 60, windowMs:60*1000})
const metadata = Object.fromEntries(Object.entries(conf.metadata.plugins)
.filter(([key]) => !["base", "core"].includes(key))
.map(([key, value]) => [key, Object.fromEntries(Object.entries(value).filter(([key]) => ["name", "icon", "web", "supports"].includes(key)))]))
const enabled = Object.entries(metadata).map(([name]) => ({name, enabled:plugins[name]?.enabled ?? false}))
const templates = Object.entries(Templates).map(([name]) => ({name, enabled:(conf.settings.templates.enabled.length ? conf.settings.templates.enabled.includes(name) : true) ?? false}))
const enabled = Object.entries(Plugins).map(([name]) => ({name, enabled:plugins[name]?.enabled ?? false}))
const actions = {flush:new Map()}
let requests = (await rest.rateLimit.get()).data.rate
setInterval(async () => requests = (await rest.rateLimit.get()).data.rate, 30*1000)
@@ -80,6 +83,7 @@
//Plugins and templates
app.get("/.plugins", limiter, (req, res) => res.status(200).json(enabled))
app.get("/.plugins.base", limiter, (req, res) => res.status(200).json(conf.settings.plugins.base.parts))
app.get("/.plugins.metadata", limiter, (req, res) => res.status(200).json(metadata))
app.get("/.templates", limiter, (req, res) => res.status(200).json(templates))
app.get("/.templates/:template", limiter, (req, res) => req.params.template in conf.templates ? res.status(200).json(conf.templates[req.params.template]) : res.sendStatus(404))
for (const template in conf.templates)
@@ -148,7 +152,7 @@
//Compute rendering
try {
//Render
const q = parse(req.query)
const q = req.query
console.debug(`metrics/app/${login} > ${util.inspect(q, {depth:Infinity, maxStringLength:256})}`)
const {rendered, mime} = await metrics({login, q}, {
graphql, rest, plugins, conf,
@@ -195,19 +199,3 @@
`Server ready !`
].join("\n")))
}
/** Query parser */
function parse(query) {
for (const [key, value] of Object.entries(query)) {
//Parse number
if (/^\d+$/.test(value))
query[key] = Number(value)
//Parse boolean
if (/^(?:true|false)$/.test(value))
query[key] = (value === "true")||(value === true)
//Parse null
if (/^null$/.test(value))
query[key] = null
}
return query
}

View File

@@ -0,0 +1,29 @@
{
"//": "Example of configuration for metrics web instance",
"//": "====================================================================",
"token": "MY GITHUB API TOKEN", "//": "GitHub Personal Token (required)",
"restricted": [], "//": "Authorized users (empty to disable)",
"maxusers": 0, "//": "Maximum users, (0 to disable)",
"cached": 3600000, "//": "Cache time rendered metrics (0 to disable)",
"ratelimiter": null, "//": "Rate limiter (see express-rate-limit documentation)",
"port": 3000, "//": "Listening port",
"optimize": true, "//": "SVG optimization",
"debug": false, "//": "Debug logs",
"mocked": false, "//": "Use mocked data instead of live APIs",
"repositories": 100, "//": "Number of repositories to use",
"community": {
"templates": [], "//": "Additional community templates to setup"
},
"templates": {
"default": "classic", "//": "Default template",
"enabled": [], "//": "Enabled templates (empty to enable all)"
},
"plugins": { "//": "Global plugin configuration",
<% for (const name of Object.keys(plugins).filter(v => !["base", "core"].includes(v))) { -%>
"<%= name %>":{
<%- JSON.stringify(Object.fromEntries(Object.entries(plugins[name].inputs).filter(([key, {type}]) => type === "token").map(([key, {description:value}]) => [key.replace(new RegExp(`^plugin_${name}_`), ""), value])), null, 6).replace(/^[{]/gm, "").replace(/^\s*[}]$/gm, "").replace(/": "/gm, `${'": null,'.padEnd(22)} "//":"`).replace(/"$/gm, '",').trimStart().replace(/\n$/gm, "\n ") %>"enabled": false, "//": "<%= plugins[name].inputs[`plugin_${name}`].description %>"
},
<% } %>"//": ""
}
}

View File

@@ -2,6 +2,7 @@
//Init
const {data:templates} = await axios.get("/.templates")
const {data:plugins} = await axios.get("/.plugins")
const {data:metadata} = await axios.get("/.plugins.metadata")
const {data:base} = await axios.get("/.plugins.base")
const {data:version} = await axios.get("/.version")
templates.sort((a, b) => (a.name.startsWith("@") ^ b.name.startsWith("@")) ? (a.name.startsWith("@") ? 1 : -1) : a.name.localeCompare(b.name))
@@ -60,111 +61,20 @@
list:plugins,
enabled:{base:Object.fromEntries(base.map(key => [key, true]))},
descriptions:{
pagespeed:"⏱️ Website performances",
languages:"🈷️ Most used languages",
followup:"🎟️ Issues and pull requests",
traffic:"🧮 Pages views",
lines:"👨‍💻 Lines of code changed",
habits:"💡 Coding habits",
music:"🎼 Music plugin",
posts:"✒️ Recent posts",
isocalendar:"📅 Isometric commit calendar",
gists:"🎫 Gists metrics",
topics:"📌 Starred topics",
projects:"🗂️ Projects",
tweets:"🐤 Latest tweets",
stars:"🌟 Recently starred repositories",
stargazers:"✨ Stargazers over last weeks",
activity:"📰 Recent activity",
people:"🧑‍🤝‍🧑 People",
anilist:"🌸 Anilist",
base:"🗃️ Base content",
"base.header":"Header",
"base.activity":"Account activity",
"base.community":"Community stats",
"base.repositories":"Repositories metrics",
"base.metadata":"Metadata",
...Object.fromEntries(Object.entries(metadata).map(([key, {name}]) => [key, name]))
},
options:{
descriptions:{
"languages.ignored":{text:"Ignored languages", placeholder:"lang-0, lang-1, ..."},
"languages.skipped":{text:"Skipped repositories", placeholder:"repo-0, repo-1, ..."},
"languages.colors":{text:"Custom language colors", placeholder:"0:#ff0000, javascript:yellow, ..."},
"pagespeed.detailed":{text:"Detailed audit", type:"boolean"},
"pagespeed.screenshot":{text:"Audit screenshot", type:"boolean"},
"pagespeed.url":{text:"Url", placeholder:"(default to GitHub attached)"},
"habits.from":{text:"Events to use", type:"number", min:1, max:1000},
"habits.days":{text:"Max events age", type:"number", min:1, max:30},
"habits.facts":{text:"Display facts", type:"boolean"},
"habits.charts":{text:"Display charts", type:"boolean"},
"music.provider":{text:"Provider", placeholder:"spotify"},
"music.playlist":{text:"Playlist url", placeholder:"https://embed.music.apple.com/en/playlist/"},
"music.limit":{text:"Limit", type:"number", min:1, max:100},
"music.user":{text:"Username", placeholder:"(default to GitHub login)"},
"posts.limit":{text:"Limit", type:"number", min:1, max:30},
"posts.user":{text:"Username", placeholder:"(default to GitHub login)"},
"posts.source":{text:"Source", type:"select", values:["dev.to"]},
"isocalendar.duration":{text:"Duration", type:"select", values:["half-year", "full-year"]},
"projects.limit":{text:"Limit", type:"number", min:0, max:100},
"projects.repositories":{text:"Repositories projects", placeholder:"user/repo/projects/1, ..."},
"projects.descriptions":{text:"Projects descriptions", type:"boolean"},
"topics.mode":{text:"Mode", type:"select", values:["starred", "mastered"]},
"topics.sort":{text:"Sort by", type:"select", values:["starred", "activity", "stars", "random"]},
"topics.limit":{text:"Limit", type:"number", min:0, max:20},
"tweets.limit":{text:"Limit", type:"number", min:1, max:10},
"tweets.user":{text:"Username", placeholder:"(default to GitHub attached)"},
"stars.limit":{text:"Limit", type:"number", min:1, max:100},
"activity.limit":{text:"Limit", type:"number", min:1, max:100},
"activity.days":{text:"Max events age", type:"number", min:1, max:9999},
"activity.filter":{text:"Events type", placeholder:"all"},
"people.size":{text:"Limit", type:"number", min:16, max:64},
"people.limit":{text:"Limit", type:"number", min:1, max:9999},
"people.types":{text:"Types", placeholder:"followers, following"},
"people.thanks":{text:"Special thanks", placeholder:"user1, user2, ..."},
"people.identicons":{text:"Use identicons", type:"boolean"},
"anilist.medias":{text:"Medias to display", placeholder:"anime, manga"},
"anilist.sections":{text:"Sections to display", placeholder:"favorites, watching, reading, characters"},
"anilist.limit":{text:"Limit", type:"number", min:0, max:9999},
"anilist.shuffle":{text:"Shuffle data", type:"boolean"},
"anilist.user":{text:"Username", placeholder:"(default to GitHub login)"},
},
"languages.ignored":"",
"languages.skipped":"",
"pagespeed.detailed":false,
"pagespeed.screenshot":false,
"habits.from":200,
"habits.days":14,
"habits.facts":true,
"habits.charts":false,
"music.provider":"",
"music.playlist":"",
"music.limit":4,
"music.user":"",
"posts.limit":4,
"posts.user":"",
"posts.source":"dev.to",
"isocalendar.duration":"half-year",
"projects.limit":4,
"projects.repositories":"",
"topics.mode":"starred",
"topics.sort":"stars",
"topics.limit":12,
"tweets.limit":2,
"tweets.user":"",
"stars.limit":4,
"activity.limit":5,
"activity.days":14,
"activity.filter":"all",
"people.size":28,
"people.limit":28,
"people.types":"followers, following",
"people.thanks":"",
"people.identicons":false,
"anilist.medias":"anime, manga",
"anilist.sections":"favorites",
"anilist.limit":2,
"anilist.shuffle":true,
"anilist.user":"",
descriptions:{...(Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))},
...(Object.fromEntries(Object.entries(
Object.assign({}, ...Object.entries(metadata).flatMap(([key, {web}]) => web)))
.map(([key, {defaulted}]) => [key, defaulted])
))
},
},
templates:{
@@ -226,7 +136,7 @@
`# Visit https://github.com/lowlighter/metrics/blob/master/action.yml for full reference`,
`name: Metrics`,
`on:`,
` # Schedule updates`,
` # Schedule updates (each hour)`,
` schedule: [{cron: "0 * * * *"}]`,
` # Lines below let you run workflow manually and on each commit`,
` push: {branches: ["master", "main"]}`,

View File

@@ -1,5 +1,5 @@
(function () {
//Load asset
(function ({axios, faker, ejs} = {axios:globalThis.axios, faker:globalThis.faker, ejs:globalThis.ejs}) {
//Load assets
const cached = new Map()
async function load(url) {
if (!cached.has(url))
@@ -19,7 +19,7 @@
return values.sort((a, b) => b - a)
}
//Placeholder function
window.placeholder = async function (set) {
globalThis.placeholder = async function (set) {
//Load templates informations
let {image, style, fonts, partials} = await load(`/.templates/${set.templates.selected}`)
await Promise.all(partials.map(async partial => await load(`/.templates/${set.templates.selected}/partials/${partial}.ejs`)))
@@ -78,6 +78,7 @@
avatar:"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mOcOnfpfwAGfgLYttYINwAAAABJRU5ErkJggg=="
},
//User data
account:"user",
user:{
databaseId:faker.random.number(10000000),
name:"(placeholder)",
@@ -127,10 +128,10 @@
id:faker.random.number(100000000000000).toString(),
created_at:faker.date.recent(),
entities: {
mentions: [ { start: 22, end: 33, username: 'lowlighter' } ]
mentions: [ {start:22, end:33, username:"lowlighter"} ]
},
text: 'Checkout metrics from <span class="mention">@lowlighter</span> ! <span class="hashtag">#GitHub</span> ',
mentions: [ 'lowlighter' ]
mentions: ["lowlighter"]
},
...new Array(Number(options["tweets.limit"])-1).fill(null).map(_ => ({
id:faker.random.number(100000000000000).toString(),
@@ -590,4 +591,10 @@
//Render
return await ejs.render(image, data, {async:true, rmWhitespace:true})
}
//Reset globals contexts
globalThis.placeholder.init = function(globals) {
axios = globals.axios || axios
faker = globals.faker || faker
ejs = globals.ejs || ejs
}
})()