Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add item sessions endpoint #2920

Merged
merged 5 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 60 additions & 19 deletions server/controllers/MeController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const { toNumber } = require('../utils/index')
const userStats = require('../utils/queries/userStats')

class MeController {
constructor() { }
constructor() {}

getCurrentUser(req, res) {
res.json(req.user.toJSONForBrowser())
Expand All @@ -33,6 +33,43 @@ class MeController {
res.json(payload)
}

/**
* GET: /api/me/item/listening-sessions/:libraryItemId/:episodeId
*
* @this import('../routers/ApiRouter')
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getItemListeningSessions(req, res) {
const libraryItem = await Database.libraryItemModel.findByPk(req.params.libraryItemId)
const episode = await Database.podcastEpisodeModel.findByPk(req.params.episodeId)

if (!libraryItem || (libraryItem.mediaType === 'podcast' && !episode)) {
Logger.error(`[MeController] Media item not found for library item id "${req.params.libraryItemId}"`)
return res.sendStatus(404)
}

const mediaItemId = episode?.id || libraryItem.mediaId
let listeningSessions = await this.getUserItemListeningSessionsHelper(req.user.id, mediaItemId)

const itemsPerPage = toNumber(req.query.itemsPerPage, 10) || 10
const page = toNumber(req.query.page, 0)

const start = page * itemsPerPage
const sessions = listeningSessions.slice(start, start + itemsPerPage)

const payload = {
total: listeningSessions.length,
numPages: Math.ceil(listeningSessions.length / itemsPerPage),
page,
itemsPerPage,
sessions
}

res.json(payload)
}

// GET: api/me/listening-stats
async getListeningStats(req, res) {
const listeningStats = await this.getUserListeningStatsHelpers(req.user.id)
Expand Down Expand Up @@ -80,7 +117,7 @@ class MeController {
if (!libraryItem) {
return res.status(404).send('Item not found')
}
if (!libraryItem.media.episodes.find(ep => ep.id === episodeId)) {
if (!libraryItem.media.episodes.find((ep) => ep.id === episodeId)) {
Logger.error(`[MeController] removeEpisode episode ${episodeId} not found for item ${libraryItem.id}`)
return res.status(404).send('Episode not found')
}
Expand Down Expand Up @@ -123,7 +160,7 @@ class MeController {

// POST: api/me/item/:id/bookmark
async createBookmark(req, res) {
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)

const { time, title } = req.body
const bookmark = req.user.createBookmark(req.params.id, time, title)
Expand All @@ -134,7 +171,7 @@ class MeController {

// PATCH: api/me/item/:id/bookmark
async updateBookmark(req, res) {
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)

const { time, title } = req.body
if (!req.user.findBookmark(req.params.id, time)) {
Expand All @@ -152,7 +189,7 @@ class MeController {

// DELETE: api/me/item/:id/bookmark/:time
async removeBookmark(req, res) {
if (!await Database.libraryItemModel.checkExistsById(req.params.id)) return res.sendStatus(404)
if (!(await Database.libraryItemModel.checkExistsById(req.params.id))) return res.sendStatus(404)

const time = Number(req.params.time)
if (isNaN(time)) return res.sendStatus(500)
Expand Down Expand Up @@ -254,11 +291,10 @@ class MeController {
// TODO: More efficient to do this in a single query
for (const mediaProgress of req.user.mediaProgress) {
if (!mediaProgress.isFinished && (mediaProgress.progress > 0 || mediaProgress.ebookProgress > 0)) {

const libraryItem = await Database.libraryItemModel.getOldById(mediaProgress.libraryItemId)
if (libraryItem) {
if (mediaProgress.episodeId && libraryItem.mediaType === 'podcast') {
const episode = libraryItem.media.episodes.find(ep => ep.id === mediaProgress.episodeId)
const episode = libraryItem.media.episodes.find((ep) => ep.id === mediaProgress.episodeId)
if (episode) {
const libraryItemWithEpisode = {
...libraryItem.toJSONMinified(),
Expand All @@ -277,7 +313,9 @@ class MeController {
}
}

itemsInProgress = sort(itemsInProgress).desc(li => li.progressLastUpdate).slice(0, limit)
itemsInProgress = sort(itemsInProgress)
.desc((li) => li.progressLastUpdate)
.slice(0, limit)
res.json({
libraryItems: itemsInProgress
})
Expand Down Expand Up @@ -317,29 +355,32 @@ class MeController {

// GET: api/me/progress/:id/remove-from-continue-listening
async removeItemFromContinueListening(req, res) {
const mediaProgress = req.user.mediaProgress.find(mp => mp.id === req.params.id)
const mediaProgress = req.user.mediaProgress.find((mp) => mp.id === req.params.id)
if (!mediaProgress) {
return res.sendStatus(404)
}
const hasUpdated = req.user.removeProgressFromContinueListening(req.params.id)
if (hasUpdated) {
await Database.mediaProgressModel.update({
hideFromContinueListening: true
}, {
where: {
id: mediaProgress.id
await Database.mediaProgressModel.update(
{
hideFromContinueListening: true
},
{
where: {
id: mediaProgress.id
}
}
})
)
SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.user.toJSONForBrowser())
}
res.json(req.user.toJSONForBrowser())
}

/**
* GET: /api/me/stats/year/:year
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*
* @param {import('express').Request} req
* @param {import('express').Response} res
*/
async getStatsForYear(req, res) {
const year = Number(req.params.year)
Expand All @@ -351,4 +392,4 @@ class MeController {
res.json(data)
}
}
module.exports = new MeController()
module.exports = new MeController()
12 changes: 9 additions & 3 deletions server/routers/ApiRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ class ApiRouter {
//
this.router.get('/me', MeController.getCurrentUser.bind(this))
this.router.get('/me/listening-sessions', MeController.getListeningSessions.bind(this))
this.router.get('/me/item/listening-sessions/:libraryItemId/:episodeId?', MeController.getItemListeningSessions.bind(this))
this.router.get('/me/listening-stats', MeController.getListeningStats.bind(this))
this.router.get('/me/progress/:id/remove-from-continue-listening', MeController.removeItemFromContinueListening.bind(this))
this.router.get('/me/progress/:id/:episodeId?', MeController.getMediaProgress.bind(this))
Expand Down Expand Up @@ -425,9 +426,9 @@ class ApiRouter {
/**
* Used when a series is removed from a book
* Series is removed if it only has 1 book
*
*
* @param {string} bookId
* @param {string[]} seriesIds
* @param {string[]} seriesIds
*/
async checkRemoveEmptySeries(bookId, seriesIds) {
if (!seriesIds?.length) return
Expand Down Expand Up @@ -455,7 +456,7 @@ class ApiRouter {

/**
* Remove an empty series & close an open RSS feed
* @param {import('../models/Series')} series
* @param {import('../models/Series')} series
*/
async removeEmptySeries(series) {
await this.rssFeedManager.closeFeedForEntityId(series.id)
Expand All @@ -474,6 +475,11 @@ class ApiRouter {
return userSessions.sort((a, b) => b.updatedAt - a.updatedAt)
}

async getUserItemListeningSessionsHelper(userId, mediaItemId) {
const userSessions = await Database.getPlaybackSessions({ userId, mediaItemId })
return userSessions.sort((a, b) => b.updatedAt - a.updatedAt)
}

async getUserListeningStatsHelpers(userId) {
const today = date.format(new Date(), 'YYYY-MM-DD')

Expand Down
Loading