import path from 'path' import express from 'express' import session from 'express-session' import bodyParser from 'body-parser' import connectSession from 'connect-redis' import redis from 'redis' import http from 'http' import https from 'https' import ffmpeg from 'fluent-ffmpeg' import { user, userMiddleware } from './user' import { dbPromise } from './database' import playlist from './playlist' import lastfm from './lastfm' require('express-async-errors') const values = require(path.join(process.cwd(), 'values.json')) const tracksPerPage = 100 const app = express() const port = process.env.PORT || 3000 const dev = process.env.NODE_ENV === 'development' const server = http.createServer(app) if (dev) { const morgan = require('morgan') app.use(morgan('dev')) } app.set('trust proxy', 1) app.use(bodyParser.urlencoded({ extended: false })) app.use(bodyParser.json()) const router = express.Router() const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year'] const srchcategories = ['title', 'artist', 'album'] let SessionStore = connectSession(session) app.use(session({ key: values.session_key || 'Session', secret: values.session_secret || 'ch4ng3 m3!', store: new SessionStore({ client: redis.createClient(values.redis || { port: 6379 }) }), resave: false, saveUninitialized: true, cookie: { secure: !dev, maxAge: 2678400000 // 1 month } })) // ------ // // TRACKS // // ------ // router.get('/tracks', userMiddleware, async (req, res) => { let page = parseInt(req.query.page) || 1 if (isNaN(page)) { page = 1 } let sort = req.query.sort if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) { sort = 'artist' } let sortdir = req.query.sortdir if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) { sortdir = 'asc' } let db = await dbPromise let count = (await db.get('SELECT COUNT(*) FROM Track'))['COUNT(*)'] let pageCount = Math.ceil(count / tracksPerPage) if (page > pageCount) page = pageCount let offset = (page - 1) * tracksPerPage let tracks = await db.all(`SELECT * FROM Track ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, tracksPerPage, offset) for (let i in tracks) { delete tracks[i].file } res.jsonp({ page, count, pageCount, tracks }) }) router.get('/tracks/search', userMiddleware, async (req, res) => { let query = req.query.q let streamable = (req.query.streamable === '1') let qr = '' let exact = false if (query.indexOf(':') !== -1) { let ctr = query.split(':') if (srchcategories.indexOf(ctr[0]) !== -1) { qr = `ifnull(${ctr[0]}, '')` query = query.substring(ctr[0].length + 1) } } if (qr === '') { for (let c in srchcategories) { let cat = srchcategories[c] if (parseInt(c) !== 0) qr += ' || ' qr += `ifnull(${cat}, '')` } } if (query.indexOf('=') !== -1 && query.indexOf('\\=') === -1) { query = query.replace('=', '') exact = true } let original = String(query) if (!exact) query = `%${query}%` let sort = req.query.sort if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) { sort = 'artist' } let sortdir = req.query.sortdir if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) { sortdir = 'asc' } // Paging let page = parseInt(req.query.page) || 1 if (isNaN(page)) { page = 1 } let db = await dbPromise let count = (await db.get(`SELECT COUNT(*) FROM Track WHERE ${qr} LIKE ?`, query))['COUNT(*)'] let pageCount = Math.ceil(count / tracksPerPage) if (page > pageCount) page = pageCount let offset = (page - 1) * tracksPerPage let tracks = await db.all(`SELECT * FROM Track WHERE ${qr} LIKE ? ORDER BY ${sort} ${sortdir.toUpperCase()} LIMIT ? OFFSET ?`, query, tracksPerPage, offset) let llimit = tracksPerPage - count if (streamable && page === pageCount && llimit > 1) { if (llimit < 10) llimit = 10 try { let lfm = await lastfm.search(original, llimit) if (lfm && lfm.length) { tracks = tracks.concat(lfm) count = count + lfm.length if (page == 0) page = 1 if (pageCount == 0) pageCount = 1 } } catch (e) {} } for (let i in tracks) { delete tracks[i].file } res.jsonp({ page, count, pageCount, tracks }) }) router.get('/track/:id', userMiddleware, async (req, res, next) => { let id = req.params.id let db = await dbPromise let track = await db.get('SELECT * FROM Track WHERE id = ?', id) if (!track) { track = await lastfm.getTrackMetaReal(id) if (!track) throw new Error('404 track not found') } delete track.file res.jsonp(track) }) // --------- // // PLAYLISTS // // --------- // // General playlist endpoints router.post('/playlist/new', userMiddleware, async (req, res, next) => { if (!req.body.title) throw new Error('Title missing from body.') let id = await playlist.createPlaylist(req.session.user, req.body.title) res.jsonp({success: true, playlist: id.id}) }) router.post('/playlist/delete/:playlistId', userMiddleware, async (req, res, next) => { let pId = req.params.playlistId await playlist.deletePlaylist(req.session.user, pId) res.jsonp({success: true}) }) // Playlist track endpoints router.post('/playlist/track/put/:playlistId/:trackId', userMiddleware, async (req, res, next) => { let pId = req.params.playlistId let tId = req.params.trackId await playlist.addTrack(req.session.user, pId, tId) res.jsonp({success: true}) }) router.post('/playlist/track/remove/:playlistId/:trackId', userMiddleware, async (req, res, next) => { let pId = req.params.playlistId let tId = req.params.trackId await playlist.removeTrack(req.session.user, pId, tId) res.jsonp({success: true}) }) router.post('/playlist/track/move/:playlistId/:trackId', userMiddleware, async (req, res, next) => { let pId = req.params.playlistId let tId = req.params.trackId let pos = parseInt(req.body.position) if (!pos || isNaN(pos)) throw new Error('Invalid position.') await playlist.moveTrack(req.session.user, pId, tId, pos) res.jsonp({success: true}) }) router.get('/playlist', userMiddleware, async (req, res, next) => { res.jsonp(await playlist.getPlaylists(req.session.user)) }) router.get('/playlist/:id', userMiddleware, async (req, res, next) => { let sort = req.query.sort if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) { sort = 'artist' } let sortdir = req.query.sortdir if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) { sortdir = 'asc' } if (sort === 'id') { sort = 'ntry.indx' } else { sort = 'trck.' + sort } res.jsonp(await playlist.getPlaylist(req.params.id, sort, sortdir)) }) // ----------- // // FILE SERVER // // ----------- // router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => { let id = req.params.id let dl = (req.query.dl === '1') let db = await dbPromise let track = await db.get('SELECT file FROM Track WHERE id = ?', id) if (!track) { track = await lastfm.getTrackMetaReal(id) if (!track) throw new Error('404 file not found') if (dl) { lastfm.invokeDownload(id) return res.end('

OK

') } dev && console.log("Remote", track.file) return ffmpeg(track.file) .audioCodec('libmp3lame') .format('mp3') .on('error', (e) => console.error(e)) .pipe(res, {end: true}) } let fpath = path.resolve(track.file) res.set('Cache-Control', 'public, max-age=31557600') if (dl) return res.download(fpath) res.redirect('/file/track' + fpath.substring(values.directory.length)) }) // ---------- // // ERROR SINK // // ---------- // router.use((err, req, res, next) => { let msg = err.message dev && console.error(err.stack) res.status(msg.indexOf('404') !== -1 ? 404 : 400).jsonp({ error: err.message, stack: dev ? err.stack.toString() : undefined }) }) app.use('/user', user(values.oauth, values.registrations === true)) app.use('/api', router) app.use('/file/track', express.static(path.resolve(values.directory))) app.use('/', express.static(path.join(process.cwd(), 'public'))) const host = process.env.NODE_ENV === 'development' ? '0.0.0.0' : '127.0.0.1' server.listen(port, host, function () { console.log(`app running on port ${port}`) })