132 lines
3.4 KiB
JavaScript
132 lines
3.4 KiB
JavaScript
const path = require('path')
|
|
const express = require('express')
|
|
const bodyParser = require('body-parser')
|
|
|
|
require('express-async-errors')
|
|
|
|
const config = require(path.join(__dirname, 'config.js'))
|
|
|
|
const app = express()
|
|
const port = config.api.port
|
|
const host = config.api.host
|
|
const mount = config.api.mount
|
|
|
|
app.enable('trust proxy', 1)
|
|
app.disable('x-powered-by')
|
|
|
|
app.use(bodyParser.urlencoded({ extended: false }))
|
|
app.use(bodyParser.json())
|
|
|
|
const apiRouter = express.Router()
|
|
const internalRouter = express.Router()
|
|
|
|
// Make sure internal router can only be accessed from local network
|
|
internalRouter.use(function (req, res, next) {
|
|
if (req.ip !== '127.0.0.1' && req.ip !== '::1') {
|
|
return res.status(401).end('401 Unauthorized. Accessed internal API endpoint!')
|
|
}
|
|
|
|
next()
|
|
})
|
|
|
|
// Call this here to prevent key from being required on internal endpoints
|
|
apiRouter.use('/internal', internalRouter)
|
|
|
|
// Key-based authorization for API endpoints
|
|
apiRouter.use(function (req, res, next) {
|
|
let key = req.query.key
|
|
|
|
// Prefer headers for authentication
|
|
if (req.headers['authorization'] && typeof req.headers['authorization'] === 'string') {
|
|
let auth = req.headers['authorization']
|
|
if (auth.indexOf('Bearer') !== -1) return next(new Error('Invalid authorization header!'))
|
|
auth = auth.split(' ')[1]
|
|
|
|
if (!auth) return next(new Error('Invalid authorization header!'))
|
|
key = auth
|
|
}
|
|
|
|
if (!key || config.api.keys.indexOf(key) === -1) return res.status(403).end('403 Forbidden')
|
|
req.key = key
|
|
next()
|
|
})
|
|
|
|
module.exports = function (liq, sched) {
|
|
// Schedule actions
|
|
apiRouter.get('/events', (req, res) => {
|
|
res.jsonp({
|
|
calendar: config.calendar.calendar,
|
|
events: sched.events
|
|
})
|
|
})
|
|
|
|
apiRouter.post('/events/reload', async (req, res) => {
|
|
await sched.calendarFetch()
|
|
await sched.update()
|
|
res.status(204).end()
|
|
})
|
|
|
|
// Liquidsoap status
|
|
apiRouter.get('/', (req, res) => {
|
|
res.jsonp({
|
|
time: new Date(),
|
|
authorization: req.key,
|
|
liquidsoap: {
|
|
status: liq.running,
|
|
meta: liq.metadata
|
|
}
|
|
})
|
|
})
|
|
|
|
apiRouter.get('/meta', (req, res) => {
|
|
res.jsonp(liq.metadata)
|
|
})
|
|
|
|
// Liquidsoap actions
|
|
apiRouter.post('/queue', (req, res) => {
|
|
let items = req.body.items
|
|
if (typeof items === 'string') items = [items]
|
|
else if (!items || !items.length) return res.status(400).end('400 Bad Request')
|
|
liq.queue(items)
|
|
res.status(202).end('OK')
|
|
})
|
|
|
|
apiRouter.post('/skip', (req, res) => {
|
|
liq.skip()
|
|
res.status(202).end('OK')
|
|
})
|
|
|
|
// Internal router (Only accessible from localhost, no matter the host setting)
|
|
// Used by liquidsoap
|
|
internalRouter.post('/meta', (req, res) => {
|
|
let m = req.body
|
|
|
|
if (Object.keys(m).length === 0) {
|
|
return res.send('(none)')
|
|
}
|
|
|
|
liq.metadata = m
|
|
res.send(JSON.stringify(m, null, 1))
|
|
})
|
|
|
|
app.use(mount, apiRouter)
|
|
|
|
// Handle 404
|
|
app.use((req, res, next) => {
|
|
res.status(404).end('404 Not Found')
|
|
})
|
|
|
|
// Handle errors
|
|
app.use((error, req, res, next) => {
|
|
console.error(error.stack)
|
|
if (app.get('env') === 'production') {
|
|
return res.status(500).end('Internal Server Error: ' + error.message)
|
|
}
|
|
|
|
res.status(500).end(error.stack)
|
|
})
|
|
|
|
// And finally, listen on the configured port and host
|
|
app.listen(port, host, () => console.log(`API server listening on ${host}:${port}${mount}`))
|
|
}
|