Simple playlists, download automatic db insert

This commit is contained in:
Evert Prants 2019-12-17 19:52:18 +02:00
parent 5b5414a189
commit a04bd3e69b
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
12 changed files with 454 additions and 82 deletions

View File

@ -21,6 +21,7 @@ CREATE TABLE PlaylistEntry (
id INTEGER PRIMARY KEY,
playlistId INTEGER,
trackId INTEGER,
indx INTEGER,
CONSTRAINT PE_fk_playlistId FOREIGN KEY (playlistId)
REFERENCES Playlist (id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT PE_fk_trackId FOREIGN KEY (trackId)

View File

@ -13,16 +13,18 @@
"dependencies": {
"@babel/runtime": "^7.7.6",
"bcrypt": "^3.0.7",
"bluebird": "^3.5.2",
"connect-redis": "^3.4.1",
"express": "^4.16.3",
"express-async-errors": "^3.0.0",
"express-session": "^1.16.2",
"bluebird": "^3.7.2",
"body-parser": "^1.19.0",
"connect-redis": "^3.4.2",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-session": "^1.17.0",
"fluent-ffmpeg": "^2.1.2",
"fs-extra": "^7.0.0",
"fs-extra": "^7.0.1",
"oauth-libre": "^0.9.17",
"socket.io": "^2.1.1",
"sqlite": "^3.0.2",
"redis": "^2.8.0",
"socket.io": "^2.3.0",
"sqlite": "^3.0.3",
"sqlite3": "^4.1.1"
},
"devDependencies": {

View File

@ -177,7 +177,7 @@ tr.external td {
width: 0%;
background-color: #00b7ff;
}
.ctx-menu {
.ctx-menu, .ctx-sub-items {
position: absolute;
background-color: #0c2233;
border: 2px solid #031421;
@ -200,6 +200,17 @@ tr.external td {
.ctx-item:hover, .dropdown-content div:hover {
background-color: #0c273c;
}
.ctx-multi {
position: relative;
}
.ctx-multi:hover > .ctx-sub-items {
display: block !important;
}
.ctx-sub-items {
left: 100%;
top: 0;
display: none !important;
}
.inline-flex {
display: flex;
flex-direction: row;
@ -210,6 +221,9 @@ tr.external td {
flex-grow: 1;
min-width: 0;
}
.player .inline-flex {
overflow: hidden;
}
#search-clear {
display: none;
}

View File

@ -72,6 +72,10 @@
<li><a class="ctx-item" data-action="play">Play Track</a></li>
<li><a class="ctx-item" data-action="queue">Queue Track</a></li>
<li><a class="ctx-item" data-action="download">Download</a></li>
<li class="ctx-multi"><a class="ctx-item playlist-add" style="display: none;">Add to Playlist</a>
<ul class="ctx-sub-items playlist-list" id="ctx-playlists"></ul>
</li>
<li><a class="ctx-item" data-action="playlist-remove" style="display: none;">Remove from Playlist</a></li>
</ul>
</div>
<div class="sidebar background" id="options-drop">

View File

@ -38,7 +38,11 @@
// Playlists
var playlist = null
var playlistCache = {}
var playingList = null
var pSel = document.getElementById('playlist-select')
var dropdownBtn = pSel.querySelector('.dropdown-button')
var ctxPlaylist = document.getElementById('ctx-playlists')
// Options
var options = {
@ -108,22 +112,38 @@
return array
}
function httpGet (url, callback) {
function _httpRequest (method, url, data) {
return new Promise(function (resolve, reject) {
var xmlHttp = new XMLHttpRequest()
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState === 4 && xmlHttp.status === 200) {
resolve(JSON.parse(xmlHttp.responseText))
} else if (xmlHttp.readyState === 4) {
reject(new Error(xmlHttp.status))
let msg = 'Server returned code ' + xmlHttp.status
try {
let ct = JSON.parse(xmlHttp.responseText)
if (ct.error) msg = ct.error
} catch (e) {}
reject(new Error(msg))
}
}
xmlHttp.withCredentials = true
xmlHttp.open('GET', url, true)
xmlHttp.send(null)
xmlHttp.open(method, url, true)
if (method !== 'GET' && method !== 'HEAD') {
xmlHttp.setRequestHeader('Content-Type', 'application/json;charset=UTF-8')
}
xmlHttp.send(data ? JSON.stringify(data) : null)
})
}
function httpGet (url) {
return _httpRequest('GET', url)
}
function httpPost (url, data) {
return _httpRequest('POST', url, data)
}
function shortTitle (title, artist) {
if (!artist) return title
return artist + ' - ' + title
@ -217,7 +237,7 @@
}
function updateQT (q) {
if (playlist === 0) showTracks(pageNum)
if (playlist === -1) showTracks(pageNum)
if (q) {
qTag.style.display = 'block'
var cnt = '+' + queue.length
@ -228,6 +248,25 @@
qTag.style.display = 'none'
}
function removeFromPlaylist(track) {
if (playlist == null || playlist < 0) return
httpPost('/api/playlist/track/remove/' + playlist + '/' + track).then(function () {
alert('Track removed from playlist successfully!')
showPlaylist(playlist, true)
}).catch(function (e) {
alert(e.message)
})
}
function addToPlaylist (playlistId, trackId) {
httpPost('/api/playlist/track/put/' + playlistId + '/' + trackId).then(function () {
alert('Track added to playlist successfully!')
showPlaylist(playlistId, true)
}).catch(function (e) {
alert(e.message)
})
}
function ctxHandle (el) {
if (ctxState === 0) return
let dt = el.getAttribute("data-action")
@ -236,7 +275,7 @@
play(ctxState)
break
case 'queue':
if (playlist === 0)
if (playlist === -1)
removeFromQueue(ctxState)
else
queue.push(ctxState)
@ -244,6 +283,17 @@
case 'download':
window.open('/api/serve/by-id/' + ctxState + '?dl=1', '_blank')
break
case 'playlist-remove':
removeFromPlaylist(ctxState)
break
case 'playlist-new':
createPlaylist(ctxState)
break
}
// Adding to playlist
if (dt && dt.indexOf('push-') === 0) {
let id = dt.substr(5)
addToPlaylist(id, ctxState)
}
ctxHide()
updateQT(qTag.style.display === 'block')
@ -256,6 +306,12 @@
let qbtn = menu.querySelector('.ctx-item[data-action="queue"]')
qbtn.innerHTML = qe ? 'Remove from Queue' : 'Queue Track'
let plAdd = menu.querySelector('.playlist-add')
plAdd.style.display = isNaN(parseInt(xid)) ? 'none' : 'block'
let plDel = menu.querySelector('.ctx-item[data-action="playlist-remove"]')
plDel.style.display = (playlist != null && playlist >= 0) ? 'block' : 'none'
ctxState = xid
}
@ -275,7 +331,7 @@
}
function handleHeads () {
if (playlist === 0) return
if (playlist === -1) return
let els = elementsToArray(document.querySelectorAll('th'))
let sortarr = document.createElement('span')
sortarr.className = 'order' + (options.sortdir === 'desc' ? ' dn' : '')
@ -291,11 +347,13 @@
if (options.sortby !== sortattr) {
options.sortdir = 'asc'
options.sortby = sortattr
return showTracks(pageNum)
if (playlist == null || playlist < 0) return showTracks(pageNum)
showPlaylist(playlist)
}
options.sortdir = (options.sortdir === 'asc' ? 'desc' : 'asc')
showTracks(pageNum)
if (playlist == null || playlist < 0) return showTracks(pageNum)
showPlaylist(playlist)
})
}
}
@ -308,12 +366,12 @@
let tag = trackDataRow(track)
tag.addEventListener('click', function (e) {
play(track.id)
play(track.trackId || track.id)
}, false)
tag.addEventListener('contextmenu', function (e) {
e.preventDefault()
ctxTrack(track.id, e)
ctxTrack(track.trackId || track.id, e)
}, false)
table.appendChild(tag)
@ -324,7 +382,7 @@
function recursionQueueList (index) {
let qid = queue[index]
if (!qid || playlist !== 0) return
if (!qid || playlist !== -1) return
httpGet('/api/track/' + qid).then(function (data) {
let tag = trackDataRow(data)
@ -358,6 +416,76 @@
pageNumText.innerHTML = pageNum + ' / ' + pages
}
function populatePlaylists () {
// Remove existing playlist names from the dropdown
let elems = elementsToArray(pSel.querySelectorAll('.playlist'))
for (let i in elems) {
let element = elems[i]
element.parentNode.removeChild(element)
}
// Get user playlists and add them to the dropdown
httpGet('/api/playlist').then(function (content) {
ctxPlaylist.innerHTML = ''
for (let i in content) {
let plylit = content[i]
// Add selection
let elem = document.createElement('div')
elem.className = 'playlist'
elem.innerHTML = plylit.title
elem.setAttribute('data-value', plylit.id)
pSel.querySelector('.dropdown-content').appendChild(elem)
// Add CTX clues
let li = document.createElement('li')
let ctxItem = document.createElement('div')
ctxItem.className = 'ctx-item'
ctxItem.innerHTML = plylit.title
ctxItem.setAttribute('data-action', 'push-' + plylit.id)
li.appendChild(ctxItem)
ctxPlaylist.appendChild(li)
}
let lit = document.createElement('li')
let ctxItemGeneral = document.createElement('div')
ctxItemGeneral.className = 'ctx-item'
ctxItemGeneral.innerHTML = 'New..'
ctxItemGeneral.setAttribute('data-action', 'playlist-new')
lit.appendChild(ctxItemGeneral)
ctxPlaylist.appendChild(lit)
handleSelect()
})
}
function showPlaylist (pid, updateOnly) {
if (!(updateOnly && playlist !== pid)) {
playlist = pid
pages = 1
pageNum = 1
}
httpGet('/api/playlist/' + pid + '?page=' + pageNum + '&sort=' + options.sortby + '&sortdir=' + options.sortdir).then(function (content) {
playlistCache[content.id] = content
if (!(updateOnly && playlist !== pid)) {
constructList(content.tracks)
updatePaging()
}
})
}
function createPlaylist (toAdd) {
let title = window.prompt('Enter name for playlist')
if (!title || title === '') return
httpPost('/api/playlist/new', { title: title }).then(function (d) {
if (toAdd == null) alert('Playlist created successfully!')
populatePlaylists()
if (toAdd != null) {
addToPlaylist(d.playlist, toAdd)
}
}).catch(function (e) {
alert(e.message)
})
}
function showTracks (page) {
let query = input.value
if (searching && query.trim() === '') {
@ -370,10 +498,13 @@
if (page > pages) page = pages
updatePaging()
if (playlist === 0) {
if (playlist === -1) {
return constructQueue()
}
playlist = null
dropdownBtn.innerHTML = 'All Tracks'
if (query.trim() === '') {
return httpGet('/api/tracks?page=' + page + '&sort=' + options.sortby + '&sortdir=' + options.sortdir).then(function (data) {
pageNum = page
@ -399,7 +530,7 @@
})
}
function play (id, q) {
function play (id, q, fromNext) {
if (id < 1) return playNext()
httpGet('/api/track/' + id).then(function (data) {
let title = shortTitle(data.title, data.artist)
@ -415,19 +546,57 @@
updateQT(q)
nowPlaying = id
if (!fromNext) {
if (playlist != null && playlist > -1 && !q) {
playingList = playlist
} else {
playingList = null
}
}
}, function (e) {
console.log(e)
})
}
function playNext () {
if (queue.length !== 0) return play(queue.shift(), true)
function playPrevious () {
if (playingList) {
let pl = playlistCache[playingList]
if (pl) {
let prevTrack = null
for (let i in pl.tracks) {
let tr = pl.tracks[i]
if (tr.trackId === nowPlaying) {
prevTrack = parseInt(i) - 1
break
}
}
if (prevTrack == null || !pl.tracks[prevTrack]) return
return play(pl.tracks[prevTrack].trackId, false, true)
}
}
if (externalStream) return
play(nowPlaying + 1)
play(nowPlaying - 1, false, true)
}
function showPlaylist (pid) {
function playNext () {
if (queue.length !== 0) return play(queue.shift(), true, true)
if (playingList) {
let pl = playlistCache[playingList]
if (pl) {
let nextTrack = null
for (let i in pl.tracks) {
let tr = pl.tracks[i]
if (tr.trackId === nowPlaying) {
nextTrack = parseInt(i) + 1
break
}
}
if (nextTrack == null || !pl.tracks[nextTrack]) return
return play(pl.tracks[nextTrack].trackId, false, true)
}
}
if (externalStream) return
play(nowPlaying + 1, false, true)
}
var ignoreFirst = false
@ -441,33 +610,37 @@
}
function handleSelect () {
let btn = pSel.querySelector('.dropdown-button')
let btns = elementsToArray(pSel.querySelectorAll('.dropdown-content div'))
for (let i in btns) {
let btni = btns[i]
let cnt = btni.getAttribute('data-value')
if (btni.getAttribute('data-bound')) continue
btni.addEventListener('click', function (e) {
let last = btn.innerHTML
let isPlaylist = false
let last = dropdownBtn.innerHTML
let set = btni.innerHTML
switch (cnt) {
case 'all':
playlist = null
break
case 'queue':
playlist = 0
playlist = -1
break
case 'options':
toggleOptions()
set = last
break
default:
isPlaylist = true
showPlaylist(cnt)
}
btn.innerHTML = set
showTracks(pageNum)
dropdownBtn.innerHTML = set
if (!isPlaylist) showTracks(pageNum)
}, false)
btni.setAttribute('data-bound', true)
}
}
@ -532,9 +705,7 @@
document.getElementById('player-next').addEventListener('click', playNext, false)
document.getElementById('player-prev').addEventListener('click', function (e) {
play(nowPlaying - 1)
}, false)
document.getElementById('player-prev').addEventListener('click', playPrevious, false)
document.getElementById('jump-first').addEventListener('click', function (e) {
showTracks(1)
@ -600,6 +771,7 @@
loadOptions()
showTracks(1)
handleHash(window.location.hash)
populatePlaylists()
handleSelect()
handleOptions()
})()

11
src/database.js Normal file
View File

@ -0,0 +1,11 @@
import path from 'path'
import sqlite from 'sqlite'
import Promise from 'bluebird'
const values = require(path.join(process.cwd(), 'values.json'))
const dbPromise = Promise.resolve()
.then(() => sqlite.open(path.join(process.cwd(), values.database), { Promise, cache: true }))
.then(db => db.migrate())
export { dbPromise }

View File

@ -1,22 +1,17 @@
import fs from 'fs-extra'
import path from 'path'
import sqlite from 'sqlite'
import Promise from 'bluebird'
import readline from 'readline'
import asn from './common/async'
import dl from './common/download'
import { dbPromise } from './database'
const values = require(path.join(process.cwd(), 'values.json'))
const musicdir = path.resolve(values.directory)
const dbPromise = Promise.resolve()
.then(() => sqlite.open(path.join(process.cwd(), values.database), { Promise, cache: true }))
.then(db => db.migrate())
// ffprobe -i <file> -show_entries format=duration -v quiet -of csv="p=0"
async function interactive (fpath, db) {
async function interactive (fpath) {
let rl = readline.createInterface({
input: process.stdin,
output: process.stdout

View File

@ -6,6 +6,7 @@ import path from 'path'
import asn from './common/async'
import dl from './common/download'
import { dbPromise } from './database'
const values = require(path.join(process.cwd(), 'values.json'))
const musicdir = path.resolve(values.directory)
@ -52,6 +53,18 @@ async function download (furl) {
await asn.promiseExec(`ffmpeg -i "${file.source}" -metadata artist="${clean.artist}" -metadata title="${clean.title}" -codec copy "${fn}"`)
await fs.unlink(file.source)
let addAnsw = await asn.askAsync(rl, `Would you like to add it to the database now? [N/y] ? `)
if (addAnsw && addAnsw.trim().toLowerCase().indexOf('y') === 0) {
// Add to database
try {
let verify = await dl.getInfos(fn)
await asn.insertDB(await dbPromise, verify)
} catch (e) {
console.warn('=!= Add to database failed!')
console.error(e.stack)
}
}
console.log('=> Done.')
rl.close()
}

View File

@ -2,6 +2,7 @@ import path from 'path'
import asn from './common/async'
import dl from './common/download'
import crypto from 'crypto'
import { dbPromise } from './database'
const fs = require('fs').promises
const values = require(path.join(process.cwd(), 'values.json'))
@ -10,7 +11,6 @@ const memexpire = 1800
let externalTracks = {}
let downloadQueue = []
let downloading = false
let dbPromise
function createHash (data) {
return crypto
@ -154,7 +154,4 @@ function invokeDownload (add) {
}, 2 * 1000)
}
export default function (db) {
dbPromise = db
return { search, getTrackMetaReal, invokeDownload }
}
export default { search, getTrackMetaReal, invokeDownload }

102
src/playlist.js Normal file
View File

@ -0,0 +1,102 @@
import path from 'path'
import { dbPromise } from './database'
async function getPlaylist (id, order = 'ntry.indx', direction = 'ASC') {
let db = await dbPromise
let p = await db.get('SELECT * FROM Playlist WHERE id = ?', id)
if (!p) throw new Error('No such playlist!')
let q = await db.all('SELECT ntry.id,ntry.trackId,ntry.playlistId,ntry.indx,ntry.userId, \
trck.title,trck.artist,trck.genre,trck.year,trck.duration,trck.track,trck.album \
FROM PlaylistEntry ntry LEFT JOIN Track \
trck ON ntry.trackId = trck.id WHERE ntry.playlistId = ? ORDER BY ' + order + ' ' + direction.toUpperCase(), id)
p.tracks = q || []
return p
}
async function getPlaylists (userId) {
let db = await dbPromise
return db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', userId)
}
async function createPlaylist (userId, title) {
let db = await dbPromise
let alreadyExists = await db.get('SELECT * FROM Playlist WHERE userId = ? AND title = ?', userId, title)
if (alreadyExists) throw new Error('Playlist with the same name already exists!')
await db.run('INSERT INTO Playlist (title,userId) VALUES (?,?)', title, userId)
return db.get('SELECT id FROM Playlist WHERE title = ? AND userId = ?', title, userId)
}
async function deletePlaylist (userId, playlistId) {
let db = await dbPromise
let ply = await db.get('SELECT * FROM Playlist WHERE id = ?', playlistId)
if (!ply) throw new Error('Could not find playlist specified.')
if (ply.userId !== userId) throw new Error(ply.userId == null ? 'This public playlist cannot be deleted through the interface.' : 'Permission denied.')
db.run('DELETE Playlist WHERE id = ?', playlistId)
db.run('DELETE PlaylistEntry WHERE playlistId = ?', playlistId)
return true
}
async function addTrack (userId, playlistId, trackId) {
let db = await dbPromise
let p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
let alreadyExists = false
for (let i in p.tracks) {
let tr = p.tracks[i]
if (tr.trackId === parseInt(trackId)) {
alreadyExists = true
break
}
}
if (alreadyExists) throw new Error('This track is already in the playlist.')
return db.run('INSERT INTO PlaylistEntry (playlistId,trackId,userId,indx) VALUES (?,?,?,?)', playlistId, trackId, userId, p.tracks.length)
}
async function removeTrack (userId, playlistId, trackId) {
let db = await dbPromise
let p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
let trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
if (!trackEntry) throw new Error('This track is not in the playlist.')
return db.run('DELETE FROM PlaylistEntry WHERE id = ?', trackEntry.id)
}
async function moveTrack (userId, playlistId, trackId, position) {
let db = await dbPromise
let p = await getPlaylist(playlistId)
if (!p) throw new Error('Invalid playlist.')
if (p.userId != null && p.userId !== userId) throw new Error('Permission denied.')
let trackEntry = await db.get('SELECT * FROM PlaylistEntry WHERE playlistId = ? AND trackId = ?', playlistId, trackId)
if (!trackEntry) throw new Error('This track is not in the playlist.')
let trcksNew = []
for (let i in p.tracks) {
let trck = p.tracks[i]
if (trck.trackId === trackId) {
trck.indx = position
continue
}
if (trck.indx >= position) {
trck.indx++
}
trcksNew.push(trck)
}
// Update indexes
for (let i in trcksNew) {
let trck = trcksNew[i]
await db.run('UPDATE PlaylistEntry SET indx = ? WHERE trackId = ? AND playlistId = ?', trck.indx, trck.trackId, playlistId)
}
return true
}
export default { getPlaylist, getPlaylists, createPlaylist, deletePlaylist, addTrack, removeTrack, moveTrack }

View File

@ -1,50 +1,48 @@
import path from 'path'
import sqlite from 'sqlite'
import Promise from 'bluebird'
import express from 'express'
import session from 'express-session'
import redis from 'connect-redis'
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 lfmda from './lastfm'
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 dbPromise = Promise.resolve()
.then(() => sqlite.open(path.join(process.cwd(), values.database), { Promise, cache: true }))
.then(db => db.migrate())
const app = express()
const port = process.env.PORT || 3000
const dev = process.env.NODE_ENV === 'development'
const server = http.createServer(app)
const lastfm = lfmda(dbPromise)
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', 'file']
const sortfields = ['id', 'track', 'artist', 'title', 'album', 'year']
const srchcategories = ['title', 'artist', 'album']
let SessionStore = redis(session)
let SessionStore = connectSession(session)
app.use(session({
key: values.session_key || 'Session',
secret: values.session_secret || 'ch4ng3 m3!',
store: new SessionStore(values.redis || { port: 6379 }),
store: new SessionStore({ client: redis.createClient(values.redis || { port: 6379 }) }),
resave: false,
saveUninitialized: true,
cookie: {
@ -53,6 +51,10 @@ app.use(session({
}
}))
// ------ //
// TRACKS //
// ------ //
router.get('/tracks', userMiddleware, async (req, res) => {
let page = parseInt(req.query.page) || 1
if (isNaN(page)) {
@ -175,7 +177,7 @@ router.get('/track/:id', userMiddleware, async (req, res, next) => {
let track = await db.get('SELECT * FROM Track WHERE id = ?', id)
if (!track) {
track = await lastfm.getTrackMetaReal(id)
if (!track) return next(new Error('404 file not found'))
if (!track) throw new Error('404 track not found')
}
delete track.file
@ -183,26 +185,77 @@ router.get('/track/:id', userMiddleware, async (req, res, next) => {
res.jsonp(track)
})
router.get('/playlists', userMiddleware, async (req, res, next) => {
let db = await dbPromise
let playlists = await db.all('SELECT * FROM Playlist WHERE userId = ? OR userId = NULL', req.session.user)
// --------- //
// PLAYLISTS //
// --------- //
res.jsonp(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 id = req.params.id
let db = await dbPromise
let playlist = await db.get('SELECT title FROM Playlist WHERE id = ?', id)
let sort = req.query.sort
if (!sort || sortfields.indexOf(sort.toLowerCase()) === -1) {
sort = 'artist'
}
if (!playlist) return next(new Error('404 file not found'))
let sortdir = req.query.sortdir
if (!sortdir || (sortdir !== 'desc' && sortdir !== 'asc')) {
sortdir = 'asc'
}
let tracks = await db.all('SELECT trackId FROM PlaylistEntry WHERE playlistId = ?', id)
playlist.tracks = tracks
if (sort === 'id') {
sort = 'ntry.indx'
} else {
sort = 'trck.' + sort
}
res.jsonp(playlist)
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')
@ -210,7 +263,7 @@ router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => {
let track = await db.get('SELECT file FROM Track WHERE id = ?', id)
if (!track) {
track = await lastfm.getTrackMetaReal(id)
if (!track) return next(new Error('404 file not found'))
if (!track) throw new Error('404 file not found')
if (dl) {
lastfm.invokeDownload(id)
return res.end('<p>OK</p><script>window.close();</script>')
@ -231,12 +284,17 @@ router.get('/serve/by-id/:id', userMiddleware, async (req, res, next) => {
res.redirect('/file/track' + fpath.substring(values.directory.length))
})
// ---------- //
// ERROR SINK //
// ---------- //
router.use((err, req, res, next) => {
console.error(err)
res.status(404).jsonp({error: 404})
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(dbPromise, values.oauth, values.registrations === true))
app.use('/user', user(values.oauth, values.registrations === true))
app.use('/api', router)
app.use('/file/track', express.static(path.resolve(values.directory)))

View File

@ -2,10 +2,12 @@ import express from 'express'
import bcrypt from 'bcrypt'
import { PromiseOAuth2 } from 'oauth-libre'
import crypto from 'crypto'
import { dbPromise } from './database'
const router = express.Router()
async function userInfoPublic (db, id) {
async function userInfoPublic (id) {
let db = await dbPromise
let u = await db.get('SELECT id, username, image FROM User WHERE id = ?', id)
if (!u) return {}
return {
@ -15,7 +17,8 @@ async function userInfoPublic (db, id) {
}
}
export async function userInfo (db, id) {
export async function userInfo (id) {
let db = await dbPromise
return db.get('SELECT * FROM User WHERE id = ?', id)
}
@ -24,14 +27,14 @@ export function userMiddleware (req, res, next) {
next()
}
export function user (dbPromise, oauth, registrations) {
export function user (oauth, registrations) {
router.get('/info', userMiddleware, async (req, res) => {
res.jsonp(await userInfoPublic(await dbPromise, req.session.user))
res.jsonp(await userInfoPublic(req.session.user))
})
router.get('/info/:id', userMiddleware, async (req, res) => {
if (isNaN(parseInt(req.params.id))) throw new Error('Invalid user ID!')
res.jsonp(await userInfoPublic(await dbPromise, parseInt(req.params.id)))
res.jsonp(await userInfoPublic(parseInt(req.params.id)))
})
if (!oauth) return router