move routes from main api

This commit is contained in:
SrGooglo 2023-05-30 01:05:51 +00:00
parent 2bab67da15
commit ff49cadde0
17 changed files with 431 additions and 39 deletions

View File

@ -12,11 +12,10 @@ import StorageClient from "@classes/StorageClient"
import RoomServer from "./roomsServer"
import pkg from "../package.json"
export default class Server {
static useMiddlewaresOrder = ["useLogger", "useCors", "useAuth"]
static useMiddlewaresOrder = ["useLogger", "useCors", "useAuth", "useErrorHandler"]
eventBus = global.eventBus = new EventEmitter()
@ -166,15 +165,15 @@ export default class Server {
await this.storage.initialize()
// register controllers & middlewares
this.server.use(express.json({ extended: false }))
this.server.use(express.urlencoded({ extended: true }))
await this.__registerControllers()
await this.__registerInternalMiddlewares()
await this.__registerInternalRoutes()
this.server.use(this.internalRouter)
this.server.use(express.json({ extended: false }))
this.server.use(express.urlencoded({ extended: true }))
await this._http.listen(this.options.listenPort, this.options.listenHost)
// calculate elapsed time

View File

@ -67,7 +67,7 @@ export default async (req, res) => {
console.log(track)
if (!track.lyricsEnabled){
if (!track.lyricsEnabled) {
return res.status(403).json({
error: "Lyrics disabled for this track",
})

View File

@ -1,11 +1,18 @@
import path from "path"
import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
import getMiddlewares from "@utils/getMiddlewares"
export default (router) => {
export default async (router) => {
// create a file based router
const routesPath = path.resolve(__dirname, "routes")
// router = createRoutesFromDirectory("routes", routesPath, router)
const middlewares = await getMiddlewares(["withOptionalAuth"])
for (const middleware of middlewares) {
router.use(middleware)
}
router = createRoutesFromDirectory("routes", routesPath, router)
return {
path: "/playlists",

View File

@ -0,0 +1,41 @@
import { Playlist, Track } from "@models"
import { NotFoundError } from "@classes/Errors"
export default async (req, res) => {
const { playlist_id } = req.params
let playlist = await Playlist.findOne({
_id: playlist_id,
}).catch((err) => {
return false
})
playlist = playlist.toObject()
if (playlist.public === false) {
if (req.session) {
if (req.session.user_id !== playlist.user_id) {
playlist = false
}
} else {
playlist = false
}
}
if (!playlist) {
return new NotFoundError(req, res, "Playlist not found")
}
const orderedIds = playlist.list
playlist.list = await Track.find({
_id: [...playlist.list],
public: true,
})
playlist.list = playlist.list.sort((a, b) => {
return orderedIds.findIndex((id) => id === a._id.toString()) - orderedIds.findIndex((id) => id === b._id.toString())
})
return res.json(playlist)
}

View File

@ -0,0 +1,44 @@
import { Playlist, Track } from "@models"
export default async (req, res) => {
const { keywords, limit = 5, offset = 0 } = req.query
let results = {
playlists: [],
artists: [],
albums: [],
tracks: [],
}
let searchQuery = {
public: true,
}
if (keywords) {
searchQuery = {
...searchQuery,
title: {
$regex: keywords,
$options: "i",
},
}
}
let playlists = await Playlist.find(searchQuery)
.limit(limit)
.skip(offset)
if (playlists) {
results.playlists = playlists
}
let tracks = await Track.find(searchQuery)
.limit(limit)
.skip(offset)
if (tracks) {
results.tracks = tracks
}
return res.json(results)
}

View File

@ -0,0 +1,46 @@
import { Playlist, Track } from "@models"
import { AuthorizationError, NotFoundError } from "@classes/Errors"
export default async (req, res) => {
if (!req.session) {
return new AuthorizationError(req, res)
}
const { keywords, limit = 10, offset = 0 } = req.query
const user_id = req.session.user_id.toString()
let searchQuery = {
user_id,
}
if (keywords) {
searchQuery = {
...searchQuery,
title: {
$regex: keywords,
$options: "i",
},
}
}
let playlists = await Playlist.find(searchQuery)
.catch((err) => false)
//.limit(limit)
//.skip(offset)
if (!playlists) {
return new NotFoundError("Playlists not found")
}
playlists = await Promise.all(playlists.map(async (playlist) => {
playlist.list = await Track.find({
_id: [
...playlist.list,
]
})
return playlist
}))
return res.json(playlists)
}

View File

@ -0,0 +1,131 @@
import { Playlist, Track } from "@models"
import { AuthorizationError, NotFoundError, PermissionError, BadRequestError } from "@classes/Errors"
const PlaylistAllowedUpdateFields = [
"title",
"cover",
"album",
"artist",
"description",
"public",
]
const TrackAllowedUpdateFields = [
"title",
"album",
"artist",
"cover",
"explicit",
"metadata",
"public",
"spotifyId",
"lyricsEnabled",
"public",
]
async function createOrUpdateTrack(payload) {
if (!payload.title || !payload.source || !payload.user_id) {
throw new Error("title and source and user_id are required")
}
let track = null
if (payload._id) {
track = await Track.findById(payload._id)
if (!track) {
throw new Error("track not found")
}
TrackAllowedUpdateFields.forEach((field) => {
if (typeof payload[field] !== "undefined") {
track[field] = payload[field]
}
})
track = await Track.findByIdAndUpdate(payload._id, track)
if (!track) {
throw new Error("Failed to update track")
}
} else {
track = new Track(payload)
await track.save()
}
return track
}
export default async (req, res) => {
if (!req.session) {
return new AuthorizationError(req, res)
}
if (!req.body.title || !req.body.list) {
return new BadRequestError(req, res, "title and list are required")
}
if (!Array.isArray(req.body.list)) {
return new BadRequestError(req, res, "list must be an array")
}
let playlist = null
if (!req.body._id) {
playlist = new Playlist({
user_id: req.session.user_id.toString(),
created_at: Date.now(),
title: req.body.title ?? "Untitled",
description: req.body.description,
cover: req.body.cover,
explicit: req.body.explicit,
public: req.body.public,
list: req.body.list,
})
await playlist.save()
} else {
playlist = await Playlist.findById(req.body._id)
}
if (!playlist) {
return new NotFoundError(req, res, "Playlist not found")
}
if (playlist.user_id !== req.session.user_id.toString()) {
return new PermissionError(req, res, "You don't have permission to edit this playlist")
}
playlist = playlist.toObject()
playlist.list = await Promise.all(req.body.list.map(async (track, index) => {
if (typeof track !== "object") {
return track
}
track.user_id = req.session.user_id.toString()
const result = await createOrUpdateTrack(track)
if (result) {
return result._id.toString()
}
}))
PlaylistAllowedUpdateFields.forEach((field) => {
if (typeof req.body[field] !== "undefined") {
playlist[field] = req.body[field]
}
})
playlist = await Playlist.findByIdAndUpdate(req.body._id, playlist)
if (!playlist) {
return new NotFoundError(req, res, "Playlist not updated")
}
global.eventBus.emit(`playlist.${playlist._id}.updated`, playlist)
return res.json(playlist)
}

View File

@ -0,0 +1,19 @@
import { Track } from "@models"
export default async (_id) => {
if (!_id) {
throw new Error("Missing _id")
}
let track = await Track.findById(_id).catch((err) => false)
if (!track) {
throw new Error("Track not found")
}
track = track.toObject()
track.artist = track.artist ?? "Unknown artist"
return track
}

View File

@ -0,0 +1,21 @@
import path from "path"
import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
import getMiddlewares from "@utils/getMiddlewares"
export default async (router) => {
// create a file based router
const routesPath = path.resolve(__dirname, "routes")
const middlewares = await getMiddlewares(["withOptionalAuth"])
for (const middleware of middlewares) {
router.use(middleware)
}
router = createRoutesFromDirectory("routes", routesPath, router)
return {
path: "/tracks",
router,
}
}

View File

@ -0,0 +1,19 @@
import { Track } from "@models"
import { NotFoundError } from "@classes/Errors"
export default async (req, res) => {
const { track_id } = req.params
let track = await Track.findOne({
_id: track_id,
public: true,
}).catch((err) => {
return null
})
if (!track) {
return new NotFoundError(req, res, "Track not found")
}
return res.json(track)
}

View File

@ -0,0 +1,43 @@
import { Track } from "@models"
import { NotFoundError, InternalServerError } from "@classes/Errors"
import mimetypes from "mime-types"
export default async (req, res) => {
const { track_id } = req.params
let track = await Track.findOne({
_id: track_id,
public: true,
}).catch((err) => {
return null
})
if (!track) {
return new NotFoundError(req, res, "Track not found")
}
track = track.toObject()
if (typeof track.stream_source === "undefined") {
return new NotFoundError(req, res, "Track doesn't have stream source")
}
global.storage.getObject(process.env.S3_BUCKET, `tracks/${track.stream_source}`, (err, dataStream) => {
if (err) {
console.error(err)
return new InternalServerError(req, res, "Error while getting file from storage")
}
const extname = mimetypes.lookup(track.stream_source)
// send chunked response
res.status(200)
// set headers
res.setHeader("Content-Type", extname)
res.setHeader("Accept-Ranges", "bytes")
return dataStream.pipe(res)
})
}

View File

@ -0,0 +1,23 @@
import { Track } from "@models"
export default async (req, res) => {
const { ids, limit = 20, offset = 0 } = req.query
if (!ids) {
return res.status(400).json({
message: "IDs is required",
})
}
let tracks = await Track.find({
_id: [...ids],
public: true,
})
.limit(limit)
.skip(offset)
.catch((err) => {
return []
})
return res.json(tracks)
}

View File

@ -2,10 +2,6 @@ export default {
name: "Track",
collection: "tracks",
schema: {
user_id: {
type: String,
required: true,
},
title: {
type: String,
required: true,
@ -44,6 +40,10 @@ export default {
lyricsEnabled: {
type: Boolean,
default: true,
}
},
publisher: {
type: Object,
required: true,
},
}
}

View File

@ -1,25 +0,0 @@
import { Track, User } from "@models"
export default async (_id) => {
if (!_id) {
throw new Error("Missing _id")
}
let track = await Track.findById(_id).catch((err) => false)
if (!track) {
throw new Error("Track not found")
}
track = track.toObject()
if (!track.metadata) {
// TODO: Get metadata from source
}
const userData = await User.findById(track.user_id).catch((err) => false)
track.artist = track.artist ?? userData?.fullName ?? userData?.username ?? "Unknown artist"
return track
}

View File

@ -7,6 +7,11 @@ export default (req, res, next) => {
res._responseTimeMs = elapsedTimeInMs
// cut req.url if is too long
if (req.url.length > 100) {
req.url = req.url.substring(0, 100) + "..."
}
console.log(`${req.method} ${res._status_code ?? res.statusCode ?? 200} ${req.url} ${elapsedTimeInMs}ms`)
})

View File

@ -1,5 +1,24 @@
import fs from "fs"
function createRouteHandler(route, fn) {
if (typeof route !== "string") {
fn = route
route = "Unknown route"
}
return async (req, res) => {
try {
await fn(req, res)
} catch (error) {
console.error(`[ERROR] (${route}) >`, error)
return res.status(500).json({
error: error.message,
})
}
}
}
function createRoutesFromDirectory(startFrom, directoryPath, router) {
const files = fs.readdirSync(directoryPath)
@ -39,7 +58,7 @@ function createRoutesFromDirectory(startFrom, directoryPath, router) {
handler = handler.default || handler
router[method](route, handler)
router[method](route, createRouteHandler(route, handler))
router.routes.push({
method,