mirror of
https://github.com/samsonjs/mit-license.git
synced 2026-03-25 09:25:49 +00:00
Refactor
Signed-off-by: Richie Bendall <richiebendall@gmail.com>
This commit is contained in:
parent
6b54c63846
commit
aa280a646b
10 changed files with 158 additions and 159 deletions
|
|
@ -1,5 +1,3 @@
|
|||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
|
|
|||
|
|
@ -1,62 +1,42 @@
|
|||
const currentYear = new Date().getFullYear()
|
||||
|
||||
module.exports = (req, res, next) => {
|
||||
const parts = req.url.split('/')
|
||||
const yearRegex = /^@?(\d{4})$/
|
||||
const yearRangeRegex = /^(\d{4})-(\d{4})$/
|
||||
|
||||
res.locals.options = parts.reduce(
|
||||
(acc, curr) => {
|
||||
if (!curr) return acc
|
||||
const getUrlParts = url => {
|
||||
if (url === '/') {
|
||||
return []
|
||||
}
|
||||
|
||||
let match = curr.match(/^@?(\d{4})$/) || []
|
||||
return url.slice(1).split('/')
|
||||
}
|
||||
|
||||
if (match.length) {
|
||||
// Pinned year
|
||||
if (curr.startsWith('@')) {
|
||||
acc.pinnedYear = parseInt(curr.substr(1), 10)
|
||||
} else {
|
||||
acc.startYear = parseInt(curr, 10)
|
||||
}
|
||||
return acc
|
||||
module.exports = (request, response, next) => {
|
||||
const urlParts = getUrlParts(request.url)
|
||||
|
||||
response.locals.options = {
|
||||
format: 'html',
|
||||
startYear: null,
|
||||
endYear: currentYear
|
||||
}
|
||||
|
||||
for (const urlPart of urlParts) {
|
||||
if (yearRegex.test(urlPart)) {
|
||||
if (urlPart.startsWith('@')) {
|
||||
response.locals.options.pinnedYear = Number.parseInt(urlPart.slice(1))
|
||||
} else {
|
||||
response.locals.options.startYear = Number.parseInt(urlPart)
|
||||
}
|
||||
} else if (yearRangeRegex.test(urlPart)) {
|
||||
const [startYear, endYear] = urlPart.match(yearRangeRegex).slice(1)
|
||||
|
||||
match = curr.match(/^(\d{4})-(\d{4})$/) || []
|
||||
|
||||
if (match.length) {
|
||||
acc.startYear = parseInt(match[1], 10)
|
||||
acc.endYear = parseInt(match[2], 10)
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
if (curr.startsWith('license')) {
|
||||
acc.format = curr
|
||||
.split('.')
|
||||
.pop()
|
||||
.trim()
|
||||
return acc
|
||||
}
|
||||
|
||||
if (curr.startsWith('+')) {
|
||||
acc.license = curr.substr(1).toUpperCase()
|
||||
return acc
|
||||
}
|
||||
|
||||
acc.sha = curr // not actually supported now - 2019-06-19
|
||||
return acc
|
||||
},
|
||||
{
|
||||
format: 'html',
|
||||
startYear: null,
|
||||
endYear: currentYear,
|
||||
sha: null
|
||||
response.locals.options.startYear = Number.parseInt(startYear)
|
||||
response.locals.options.endYear = Number.parseInt(endYear)
|
||||
} else if (urlPart.startsWith('license')) {
|
||||
response.locals.options.format = urlPart.split('.')[1].trim()
|
||||
} else if (urlPart.startsWith('+')) {
|
||||
response.locals.options.license = urlPart.slice(1).toUpperCase()
|
||||
}
|
||||
)
|
||||
|
||||
if (res.locals.options.sha) {
|
||||
res.setHeader(
|
||||
'X-note',
|
||||
'SHA and commit pinning is no longer supported, showing you latest release'
|
||||
)
|
||||
}
|
||||
|
||||
next()
|
||||
|
|
|
|||
|
|
@ -1,27 +1,26 @@
|
|||
const path = require('path')
|
||||
const loadJsonFile = require('load-json-file')
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
const id = req.hostname.split('.')[0]
|
||||
res.locals.id = id
|
||||
module.exports = async (request, response, next) => {
|
||||
response.locals.id = request.hostname.split('.')[0]
|
||||
|
||||
if (req.method.toUpperCase() !== 'GET') {
|
||||
if (request.method.toUpperCase() !== 'GET') {
|
||||
return next()
|
||||
}
|
||||
|
||||
// Otherwise load up the user json file
|
||||
res.locals.user = {
|
||||
response.locals.user = {
|
||||
copyright: '<copyright holders>'
|
||||
}
|
||||
|
||||
try {
|
||||
res.locals.user = {
|
||||
...res.locals.user,
|
||||
...await loadJsonFile(path.join(__dirname, '..', 'users', `${id}.json`))
|
||||
response.locals.user = {
|
||||
...response.locals.user,
|
||||
...await loadJsonFile(path.join(__dirname, '..', 'users', `${response.locals.id}.json`))
|
||||
}
|
||||
} catch ({ code, message }) {
|
||||
if (code !== 'ENOENT') {
|
||||
res
|
||||
response
|
||||
.code(500)
|
||||
.send(
|
||||
`An internal error occurred - open an issue on https://github.com/remy/mit-license with the following information: ${message}`
|
||||
|
|
|
|||
|
|
@ -26,16 +26,17 @@
|
|||
"dependencies": {
|
||||
"@octokit/rest": "^18.0.6",
|
||||
"@sindresorhus/is": "^3.1.2",
|
||||
"any-size": "^1.0.2",
|
||||
"any-size": "^1.2.0",
|
||||
"btoa": "^1.2.1",
|
||||
"cors": "^2.8.5",
|
||||
"create-html-element": "^3.0.0",
|
||||
"ejs": "^3.1.5",
|
||||
"escape-goat": "^3.0.0",
|
||||
"express": "^4.17.1",
|
||||
"express-minify": "^1.0.0",
|
||||
"gravatar-url": "^3.1.0",
|
||||
"html-text": "^1.0.1",
|
||||
"load-json-file": "^6.2.0",
|
||||
"md5": "^2.3.0",
|
||||
"path-exists": "^4.0.0",
|
||||
"postcss-middleware": "^1.1.4",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
|
|
|
|||
129
routes/get.js
129
routes/get.js
|
|
@ -1,97 +1,118 @@
|
|||
const md5 = require('md5')
|
||||
const path = require('path')
|
||||
const { htmlEscape, htmlUnescape } = require('escape-goat')
|
||||
const stripHtml = require('html-text')
|
||||
const is = require('@sindresorhus/is')
|
||||
const getGravatarUrl = require('gravatar-url')
|
||||
const createHtmlElement = require('create-html-element')
|
||||
const { renderFile } = require('ejs')
|
||||
|
||||
function getCopyrightHTML (user, plain) {
|
||||
let html = ''
|
||||
|
||||
const name = is.string(user)
|
||||
? user
|
||||
: plain
|
||||
? user.name || user.copyright
|
||||
: htmlEscape(user.name || user.copyright)
|
||||
|
||||
if (user.url) {
|
||||
html = `<a href="${stripHtml(user.url)}">${name}</a>`
|
||||
} else {
|
||||
html = name
|
||||
const getCopyrightName = (user, isPlainText) => {
|
||||
if (is.string(user)) {
|
||||
return user
|
||||
}
|
||||
|
||||
const copyright = user.name || user.copyright
|
||||
|
||||
return isPlainText ? copyright : htmlEscape(copyright)
|
||||
}
|
||||
|
||||
const getCopyrightHtml = (user, isPlainText) => {
|
||||
const name = getCopyrightName(user, isPlainText)
|
||||
let html = user.url ? createHtmlElement({
|
||||
name: 'a',
|
||||
attributes: {
|
||||
href: user.url
|
||||
},
|
||||
text: name
|
||||
}) : name
|
||||
|
||||
if (user.email) {
|
||||
html += ` <<a href="mailto:${stripHtml(user.email)}">${
|
||||
plain ? user.email : htmlEscape(user.email)
|
||||
}</a>>`
|
||||
html += ` <${createHtmlElement({
|
||||
name: 'a',
|
||||
attributes: {
|
||||
href: `mailto:${user.email}`
|
||||
},
|
||||
text: user.email
|
||||
})}>`
|
||||
}
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
module.exports = (req, res) => {
|
||||
const { user, options } = res.locals
|
||||
const getGravatarEmail = user => {
|
||||
if (user.gravatar && user.email) {
|
||||
// Supports regular format
|
||||
return user.email.trim().toLowerCase()
|
||||
}
|
||||
|
||||
if (is.object(user.copyright[0]) && user.gravatar) {
|
||||
// Supports multi-user format
|
||||
return user.copyright[0].email.trim().toLowerCase()
|
||||
}
|
||||
}
|
||||
|
||||
const removeFalsy = array => array.filter(Boolean)
|
||||
|
||||
module.exports = async (_, response) => {
|
||||
const { user, options } = response.locals
|
||||
const isPlainText = options.format !== 'html'
|
||||
|
||||
let name
|
||||
let gravatar
|
||||
|
||||
// No error and valid
|
||||
if (user.copyright) {
|
||||
if (is.string(user.copyright)) {
|
||||
name = getCopyrightHTML(user, options.format !== 'html')
|
||||
} else if (is.array(user.copyright) && user.copyright.every(val => is.string(val))) {
|
||||
name = getCopyrightHtml(user, isPlainText)
|
||||
} else if (is.array(user.copyright) && user.copyright.every(value => is.string(value))) {
|
||||
// Supports: ['Remy Sharp', 'Richie Bendall']
|
||||
name = user.copyright
|
||||
.map(v => (options.format !== 'html' ? v : htmlEscape(v)))
|
||||
.join(', ')
|
||||
name = user.copyright.map(value => (isPlainText ? value : htmlEscape(value))).join(', ')
|
||||
} else {
|
||||
name = user.copyright.map(getCopyrightHTML).join(', ')
|
||||
name = user.copyright.map(value => getCopyrightHtml(value)).join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
if (user.gravatar && user.email) {
|
||||
// Supports regular format
|
||||
gravatar = `<img id="gravatar" alt="Profile image" src="https://www.gravatar.com/avatar/${md5(
|
||||
user.email.trim().toLowerCase()
|
||||
)}" />`
|
||||
} else if (is.object(user.copyright[0]) && user.gravatar) {
|
||||
// Supports multi-user format
|
||||
gravatar = `<img id="gravatar" alt="Profile image" src="https://www.gravatar.com/avatar/${md5(
|
||||
user.copyright[0].email.trim().toLowerCase()
|
||||
)}" />`
|
||||
let gravatar
|
||||
const gravatarEmail = getGravatarEmail(user)
|
||||
|
||||
if (gravatarEmail) {
|
||||
gravatar = createHtmlElement({
|
||||
name: 'img',
|
||||
attributes: {
|
||||
id: 'gravatar',
|
||||
alt: 'Profile image',
|
||||
src: getGravatarUrl(gravatarEmail)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const year = options.pinnedYear
|
||||
? options.pinnedYear
|
||||
: [options.startYear, options.endYear].filter(Boolean).join('-')
|
||||
: removeFalsy([options.startYear, options.endYear]).join('-')
|
||||
const license = (options.license || user.license || 'MIT').toUpperCase()
|
||||
const format = options.format || user.format || 'html'
|
||||
|
||||
const args = {
|
||||
info: `${year} ${name}`,
|
||||
theme: user.theme || 'default',
|
||||
gravatar
|
||||
}
|
||||
|
||||
const filename = path.join(__dirname, '..', 'licenses', license)
|
||||
req.app.render(filename, args, (error, content) => {
|
||||
if (error) {
|
||||
res.status(500).send(error)
|
||||
return
|
||||
}
|
||||
try {
|
||||
const content = await renderFile(path.join(__dirname, '..', 'licenses', `${license}.ejs`), {
|
||||
info: `${year} ${name}`,
|
||||
theme: user.theme || 'default',
|
||||
gravatar
|
||||
})
|
||||
|
||||
if (format === 'txt') {
|
||||
const plain = content.match(/<article>(.*)<\/article>/ms)[1]
|
||||
|
||||
res
|
||||
response
|
||||
.set('Content-Type', 'text/plain; charset=UTF-8')
|
||||
.send(htmlUnescape(stripHtml(plain)).trim())
|
||||
return
|
||||
}
|
||||
|
||||
if (format === 'html') {
|
||||
res.send(content)
|
||||
response.send(content)
|
||||
return
|
||||
}
|
||||
|
||||
res.json({ ...user, ...options })
|
||||
})
|
||||
response.json({ ...user, ...options })
|
||||
} catch (error) {
|
||||
response.status(500).send(error)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,41 +14,45 @@ const github = new Octokit({
|
|||
const yn = require('yn')
|
||||
const is = require('@sindresorhus/is')
|
||||
|
||||
const { validDomainId } = require('./utils')
|
||||
const { isDomainId } = require('./utils')
|
||||
|
||||
function getUserData ({ query, body }) {
|
||||
// If query parameters provided
|
||||
if (size(query) > 0) return query
|
||||
if (size(query) > 0) {
|
||||
return query
|
||||
}
|
||||
|
||||
// If the data parsed as {'{data: "value"}': ''}
|
||||
if (size(body) === 1 && !Object.values(body)[0]) return JSON.parse(Object.keys(body)[0])
|
||||
if (size(body) === 1 && !Object.values(body)[0]) {
|
||||
return JSON.parse(Object.keys(body)[0])
|
||||
}
|
||||
|
||||
// Fallback
|
||||
return body
|
||||
}
|
||||
|
||||
// HTTP POST API
|
||||
module.exports = async (req, res) => {
|
||||
const { hostname } = req
|
||||
module.exports = async (request, response) => {
|
||||
const { hostname } = request
|
||||
|
||||
// Get different parts of hostname (example: remy.mit-license.org -> ['remy', 'mit-license', 'org'])
|
||||
const params = hostname.split('.')
|
||||
|
||||
// This includes the copyright, year, etc.
|
||||
const userData = getUserData(req)
|
||||
const userData = getUserData(request)
|
||||
|
||||
// If there isn't enough part of the hostname
|
||||
if (params.length < 2) {
|
||||
res.status(400).send('Please specify a subdomain in the URL.')
|
||||
response.status(400).send('Please specify a subdomain in the URL.')
|
||||
return
|
||||
}
|
||||
|
||||
// Extract the name from the URL
|
||||
const id = params[0]
|
||||
const [id] = params
|
||||
|
||||
if (!validDomainId(id)) {
|
||||
if (!isDomainId(id)) {
|
||||
// Return a vague error intentionally
|
||||
res
|
||||
response
|
||||
.status(400)
|
||||
.send(
|
||||
'User already exists - to update values, please send a pull request on https://github.com/remy/mit-license'
|
||||
|
|
@ -58,9 +62,8 @@ module.exports = async (req, res) => {
|
|||
}
|
||||
|
||||
// Check if the user file exists in the users directory
|
||||
const exists = await pathExists(path.join(__dirname, '..', 'users', `${id}.json`))
|
||||
if (exists) {
|
||||
res
|
||||
if (await pathExists(path.join(__dirname, '..', 'users', `${id}.json`))) {
|
||||
response
|
||||
.status(409)
|
||||
.send(
|
||||
'User already exists - to update values, please send a pull request on https://github.com/remy/mit-license'
|
||||
|
|
@ -72,7 +75,7 @@ module.exports = async (req, res) => {
|
|||
// Parse the string version of a boolean or similar
|
||||
userData.gravatar = yn(userData.gravatar, { lenient: true })
|
||||
if (is.undefined(userData.gravatar)) {
|
||||
res
|
||||
response
|
||||
.status(400)
|
||||
.send(
|
||||
'The "gravatar" JSON property must be a boolean.'
|
||||
|
|
@ -84,28 +87,29 @@ module.exports = async (req, res) => {
|
|||
// File doesn't exist
|
||||
// If copyright property and key doesn't exist
|
||||
if (!userData.copyright) {
|
||||
res.status(400).send('JSON requires "copyright" property and value')
|
||||
response.status(400).send('JSON requires "copyright" property and value')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await github.repos.createOrUpdateFileContents({
|
||||
owner: 'remy',
|
||||
repo: 'mit-license',
|
||||
path: `users/${id}.json`,
|
||||
message: `Automated creation of user ${id}.`,
|
||||
content: btoa(JSON.stringify(userData, 0, 2)),
|
||||
committer: {
|
||||
name: 'MIT License Bot',
|
||||
email: 'remy@leftlogic.com'
|
||||
}
|
||||
})
|
||||
await Promise.all([
|
||||
github.repos.createOrUpdateFileContents({
|
||||
owner: 'remy',
|
||||
repo: 'mit-license',
|
||||
path: `users/${id}.json`,
|
||||
message: `Automated creation of user ${id}.`,
|
||||
content: btoa(JSON.stringify(userData, 0, 2)),
|
||||
committer: {
|
||||
name: 'MIT License Bot',
|
||||
email: 'remy@leftlogic.com'
|
||||
}
|
||||
}),
|
||||
writeJsonFile(path.join(__dirname, '..', 'users', `${id}.json`), userData, { indent: undefined })
|
||||
])
|
||||
|
||||
await writeJsonFile(path.join(__dirname, '..', 'users', `${id}.json`), userData, { indent: undefined })
|
||||
|
||||
res.status(201).send(`MIT license page created: https://${hostname}`)
|
||||
} catch (err) {
|
||||
res
|
||||
response.status(201).send(`MIT license page created: https://${hostname}`)
|
||||
} catch {
|
||||
response
|
||||
.status(500)
|
||||
.send(
|
||||
'Unable to create new user - please send a pull request on https://github.com/remy/mit-license'
|
||||
|
|
|
|||
|
|
@ -1,3 +1 @@
|
|||
module.exports = {
|
||||
validDomainId: str => /^[\w-_]+$/.test(str)
|
||||
}
|
||||
exports.isDomainId = value => /^[\w-_]+$/.test(value)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ app.use(
|
|||
)
|
||||
app.use(favicon(path.join(__dirname, 'favicon.ico')))
|
||||
app.set('views', path.join(__dirname, '/licenses'))
|
||||
app.set('view engine', 'ejs')
|
||||
|
||||
// Setup static files
|
||||
app.use('/robots.txt', express.static('robots.txt'))
|
||||
|
|
@ -34,12 +33,11 @@ app.use(
|
|||
postcssMiddleware({
|
||||
plugins: [
|
||||
require('postcss-preset-env')({
|
||||
overrideBrowserslist: '>= 0%',
|
||||
stage: 0
|
||||
overrideBrowserslist: '>= 0%'
|
||||
})
|
||||
],
|
||||
src (req) {
|
||||
return path.join(__dirname, 'themes', req.path)
|
||||
src (request) {
|
||||
return path.join(__dirname, 'themes', request.path)
|
||||
}
|
||||
}),
|
||||
express.static('themes')
|
||||
|
|
|
|||
4
test.js
4
test.js
|
|
@ -1,7 +1,7 @@
|
|||
const { promises: fs } = require('fs')
|
||||
const writeJsonFile = require('write-json-file')
|
||||
const CSS = require('css')
|
||||
const { validDomainId } = require('./routes/utils')
|
||||
const { isDomainId } = require('./routes/utils')
|
||||
const hasFlag = require('has-flag')
|
||||
const getExtension = require('file-ext')
|
||||
const path = require('path-extra')
|
||||
|
|
@ -23,7 +23,7 @@ async function report (content, fix) {
|
|||
})
|
||||
}
|
||||
|
||||
if (!validDomainId(path.base(user))) {
|
||||
if (!isDomainId(path.base(user))) {
|
||||
await report(`${user} is not a valid domain id.`)
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
yarn.lock
BIN
yarn.lock
Binary file not shown.
Loading…
Reference in a new issue