From e53ac6566b46ae87618b22d2374883b2397c9e64 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sun, 11 Aug 2024 17:01:25 -0500 Subject: [PATCH] Update API JS docs --- server/Database.js | 10 -- server/controllers/AuthorController.js | 51 +++++-- server/controllers/BackupController.js | 63 ++++++++- server/controllers/CacheController.js | 22 ++- server/controllers/CollectionController.js | 62 ++++++-- .../CustomMetadataProviderController.js | 29 ++-- server/controllers/EmailController.js | 45 +++++- server/controllers/FileSystemController.js | 19 ++- server/controllers/LibraryController.js | 133 +++++++++++------- server/controllers/LibraryItemController.js | 4 +- server/controllers/MeController.js | 4 +- server/controllers/MiscController.js | 4 +- server/controllers/NotificationController.js | 4 +- server/controllers/PlaylistController.js | 4 +- server/controllers/PodcastController.js | 6 +- server/controllers/RSSFeedController.js | 4 +- server/controllers/SearchController.js | 4 +- server/controllers/SeriesController.js | 4 +- server/controllers/SessionController.js | 6 +- server/controllers/ShareController.js | 4 +- server/controllers/ToolsController.js | 4 +- server/controllers/UserController.js | 70 ++++----- server/models/User.js | 8 -- server/routers/ApiRouter.js | 1 + 24 files changed, 390 insertions(+), 175 deletions(-) diff --git a/server/Database.js b/server/Database.js index 5bae390f5e..4d55c72834 100644 --- a/server/Database.js +++ b/server/Database.js @@ -390,16 +390,6 @@ class Database { return this.models.user.updateFromOld(oldUser) } - removeUser(userId) { - if (!this.sequelize) return false - return this.models.user.removeById(userId) - } - - upsertMediaProgress(oldMediaProgress) { - if (!this.sequelize) return false - return this.models.mediaProgress.upsertFromOld(oldMediaProgress) - } - updateBulkBooks(oldBooks) { if (!this.sequelize) return false return Promise.all(oldBooks.map((oldBook) => this.models.book.saveFromOld(oldBook))) diff --git a/server/controllers/AuthorController.js b/server/controllers/AuthorController.js index 2e762bb523..22b11b3c3e 100644 --- a/server/controllers/AuthorController.js +++ b/server/controllers/AuthorController.js @@ -1,3 +1,4 @@ +const { Request, Response, NextFunction } = require('express') const sequelize = require('sequelize') const fs = require('../libs/fsExtra') const { createNewSortInstance } = require('../libs/fastSort') @@ -14,9 +15,23 @@ const { reqSupportsWebp, isValidASIN } = require('../utils/index') const naturalSort = createNewSortInstance({ comparer: new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }).compare }) + +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class AuthorController { constructor() {} + /** + * GET: /api/authors/:id + * + * @param {RequestWithUser} req + * @param {Response} res + */ async findOne(req, res) { const include = (req.query.include || '').split(',') @@ -63,9 +78,10 @@ class AuthorController { } /** + * PATCH: /api/authors/:id * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async update(req, res) { const payload = req.body @@ -194,8 +210,8 @@ class AuthorController { * DELETE: /api/authors/:id * Remove author from all books and delete * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async delete(req, res) { Logger.info(`[AuthorController] Removing author "${req.author.name}"`) @@ -218,8 +234,8 @@ class AuthorController { * POST: /api/authors/:id/image * Upload author image from web URL * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async uploadImage(req, res) { if (!req.user.canUpload) { @@ -263,8 +279,8 @@ class AuthorController { * DELETE: /api/authors/:id/image * Remove author image & delete image file * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async deleteImage(req, res) { if (!req.author.imagePath) { @@ -284,6 +300,12 @@ class AuthorController { }) } + /** + * POST: /api/authors/:id/match + * + * @param {RequestWithUser} req + * @param {Response} res + */ async match(req, res) { let authorData = null const region = req.body.region || 'us' @@ -334,7 +356,12 @@ class AuthorController { }) } - // GET api/authors/:id/image + /** + * GET: /api/authors/:id/image + * + * @param {RequestWithUser} req + * @param {Response} res + */ async getImage(req, res) { const { query: { width, height, format, raw }, @@ -358,6 +385,12 @@ class AuthorController { return CacheManager.handleAuthorCache(res, author, options) } + /** + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next + */ async middleware(req, res, next) { const author = await Database.authorModel.getOldById(req.params.id) if (!author) return res.sendStatus(404) diff --git a/server/controllers/BackupController.js b/server/controllers/BackupController.js index 99a3bf44ef..317827d09e 100644 --- a/server/controllers/BackupController.js +++ b/server/controllers/BackupController.js @@ -1,12 +1,28 @@ +const { Request, Response, NextFunction } = require('express') const Path = require('path') const fs = require('../libs/fsExtra') const Logger = require('../Logger') const Database = require('../Database') const fileUtils = require('../utils/fileUtils') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class BackupController { constructor() {} + /** + * GET: /api/backups + * + * @this import('../routers/ApiRouter') + * + * @param {RequestWithUser} req + * @param {Response} res + */ getAll(req, res) { res.json({ backups: this.backupManager.backups.map((b) => b.toJSON()), @@ -15,10 +31,26 @@ class BackupController { }) } + /** + * POST: /api/backups + * + * @this import('../routers/ApiRouter') + * + * @param {RequestWithUser} req + * @param {Response} res + */ create(req, res) { this.backupManager.requestCreateBackup(res) } + /** + * DELETE: /api/backups/:id + * + * @this import('../routers/ApiRouter') + * + * @param {RequestWithUser} req + * @param {Response} res + */ async delete(req, res) { await this.backupManager.removeBackup(req.backup) @@ -27,6 +59,14 @@ class BackupController { }) } + /** + * POST: /api/backups/upload + * + * @this import('../routers/ApiRouter') + * + * @param {RequestWithUser} req + * @param {Response} res + */ upload(req, res) { if (!req.files.file) { Logger.error('[BackupController] Upload backup invalid') @@ -41,8 +81,8 @@ class BackupController { * * @this import('../routers/ApiRouter') * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async updatePath(req, res) { // Validate path is not empty and is a string @@ -86,10 +126,10 @@ class BackupController { } /** - * api/backups/:id/download + * GET: /api/backups/:id/download * - * @param {*} req - * @param {*} res + * @param {RequestWithUser} req + * @param {Response} res */ download(req, res) { if (global.XAccel) { @@ -104,14 +144,23 @@ class BackupController { } /** + * GET: /api/backups/:id/apply + * + * @this import('../routers/ApiRouter') * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ apply(req, res) { this.backupManager.requestApplyBackup(this.apiCacheManager, req.backup, res) } + /** + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next + */ middleware(req, res, next) { if (!req.user.isAdminOrUp) { Logger.error(`[BackupController] Non-admin user "${req.user.username}" attempting to access backups`) diff --git a/server/controllers/CacheController.js b/server/controllers/CacheController.js index 85f248e667..92dbbd5f6f 100644 --- a/server/controllers/CacheController.js +++ b/server/controllers/CacheController.js @@ -1,9 +1,22 @@ +const { Request, Response } = require('express') const CacheManager = require('../managers/CacheManager') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class CacheController { constructor() {} - // POST: api/cache/purge + /** + * POST: /api/cache/purge + * + * @param {RequestWithUser} req + * @param {Response} res + */ async purgeCache(req, res) { if (!req.user.isAdminOrUp) { return res.sendStatus(403) @@ -12,7 +25,12 @@ class CacheController { res.sendStatus(200) } - // POST: api/cache/items/purge + /** + * POST: /api/cache/items/purge + * + * @param {RequestWithUser} req + * @param {Response} res + */ async purgeItemsCache(req, res) { if (!req.user.isAdminOrUp) { return res.sendStatus(403) diff --git a/server/controllers/CollectionController.js b/server/controllers/CollectionController.js index d559f3eeb3..708c00b5fc 100644 --- a/server/controllers/CollectionController.js +++ b/server/controllers/CollectionController.js @@ -1,3 +1,4 @@ +const { Request, Response, NextFunction } = require('express') const Sequelize = require('sequelize') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') @@ -5,14 +6,22 @@ const Database = require('../Database') const Collection = require('../objects/Collection') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class CollectionController { constructor() {} /** * POST: /api/collections * Create new collection - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async create(req, res) { const newCollection = new Collection() @@ -49,6 +58,12 @@ class CollectionController { res.json(jsonExpanded) } + /** + * GET: /api/collections + * + * @param {RequestWithUser} req + * @param {Response} res + */ async findAll(req, res) { const collectionsExpanded = await Database.collectionModel.getOldCollectionsJsonExpanded(req.user) res.json({ @@ -56,6 +71,12 @@ class CollectionController { }) } + /** + * GET: /api/collections/:id + * + * @param {RequestWithUser} req + * @param {Response} res + */ async findOne(req, res) { const includeEntities = (req.query.include || '').split(',') @@ -71,8 +92,9 @@ class CollectionController { /** * PATCH: /api/collections/:id * Update collection - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async update(req, res) { let wasUpdated = false @@ -123,6 +145,12 @@ class CollectionController { res.json(jsonExpanded) } + /** + * DELETE: /api/collections/:id + * + * @param {RequestWithUser} req + * @param {Response} res + */ async delete(req, res) { const jsonExpanded = await req.collection.getOldJsonExpanded() @@ -139,8 +167,9 @@ class CollectionController { * POST: /api/collections/:id/book * Add a single book to a collection * Req.body { id: } - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async addBook(req, res) { const libraryItem = await Database.libraryItemModel.getOldById(req.body.id) @@ -172,8 +201,9 @@ class CollectionController { * DELETE: /api/collections/:id/book/:bookId * Remove a single book from a collection. Re-order books * TODO: bookId is actually libraryItemId. Clients need updating to use bookId - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async removeBook(req, res) { const libraryItem = await Database.libraryItemModel.getOldById(req.params.bookId) @@ -216,8 +246,9 @@ class CollectionController { * POST: /api/collections/:id/batch/add * Add multiple books to collection * Req.body { books: } - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async addBatch(req, res) { // filter out invalid libraryItemIds @@ -274,8 +305,9 @@ class CollectionController { * POST: /api/collections/:id/batch/remove * Remove multiple books from collection * Req.body { books: } - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async removeBatch(req, res) { // filter out invalid libraryItemIds @@ -325,6 +357,12 @@ class CollectionController { res.json(jsonExpanded) } + /** + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next + */ async middleware(req, res, next) { if (req.params.id) { const collection = await Database.collectionModel.findByPk(req.params.id) diff --git a/server/controllers/CustomMetadataProviderController.js b/server/controllers/CustomMetadataProviderController.js index 8af20cee38..790a850122 100644 --- a/server/controllers/CustomMetadataProviderController.js +++ b/server/controllers/CustomMetadataProviderController.js @@ -1,20 +1,25 @@ +const { Request, Response, NextFunction } = require('express') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') const { validateUrl } = require('../utils/index') -// -// This is a controller for routes that don't have a home yet :( -// +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class CustomMetadataProviderController { constructor() {} /** * GET: /api/custom-metadata-providers * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async getAll(req, res) { const providers = await Database.customMetadataProviderModel.findAll() @@ -27,8 +32,8 @@ class CustomMetadataProviderController { /** * POST: /api/custom-metadata-providers * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async create(req, res) { const { name, url, mediaType, authHeaderValue } = req.body @@ -61,8 +66,8 @@ class CustomMetadataProviderController { /** * DELETE: /api/custom-metadata-providers/:id * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async delete(req, res) { const slug = `custom-${req.params.id}` @@ -96,9 +101,9 @@ class CustomMetadataProviderController { /** * Middleware that requires admin or up * - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next */ async middleware(req, res, next) { if (!req.user.isAdminOrUp) { diff --git a/server/controllers/EmailController.js b/server/controllers/EmailController.js index 69f4276d27..916b4268ef 100644 --- a/server/controllers/EmailController.js +++ b/server/controllers/EmailController.js @@ -1,16 +1,36 @@ +const { Request, Response, NextFunction } = require('express') const Logger = require('../Logger') const SocketAuthority = require('../SocketAuthority') const Database = require('../Database') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class EmailController { constructor() {} + /** + * GET: /api/emails/settings + * + * @param {RequestWithUser} req + * @param {Response} res + */ getSettings(req, res) { res.json({ settings: Database.emailSettings }) } + /** + * PATCH: /api/emails/settings + * + * @param {RequestWithUser} req + * @param {Response} res + */ async updateSettings(req, res) { const updated = Database.emailSettings.update(req.body) if (updated) { @@ -21,10 +41,24 @@ class EmailController { }) } + /** + * POST: /api/emails/test + * + * @this {import('../routers/ApiRouter')} + * + * @param {RequestWithUser} req + * @param {Response} res + */ async sendTest(req, res) { this.emailManager.sendTest(res) } + /** + * POST: /api/emails/ereader-devices + * + * @param {RequestWithUser} req + * @param {Response} res + */ async updateEReaderDevices(req, res) { if (!req.body.ereaderDevices || !Array.isArray(req.body.ereaderDevices)) { return res.status(400).send('Invalid payload. ereaderDevices array required') @@ -52,11 +86,12 @@ class EmailController { } /** + * POST: /api/emails/send-ebook-to-device * Send ebook to device * User must have access to device and library item * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async sendEBookToDevice(req, res) { Logger.debug(`[EmailController] Send ebook to device requested by user "${req.user.username}" for libraryItemId=${req.body.libraryItemId}, deviceName=${req.body.deviceName}`) @@ -89,6 +124,12 @@ class EmailController { this.emailManager.sendEBookToDevice(ebookFile, device, res) } + /** + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next + */ adminMiddleware(req, res, next) { if (!req.user.isAdminOrUp) { return res.sendStatus(404) diff --git a/server/controllers/FileSystemController.js b/server/controllers/FileSystemController.js index 2104ec25d4..e923c4951a 100644 --- a/server/controllers/FileSystemController.js +++ b/server/controllers/FileSystemController.js @@ -1,16 +1,24 @@ +const { Request, Response } = require('express') const Path = require('path') const Logger = require('../Logger') const fs = require('../libs/fsExtra') const { toNumber } = require('../utils/index') const fileUtils = require('../utils/fileUtils') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class FileSystemController { constructor() {} /** * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async getPaths(req, res) { if (!req.user.isAdminOrUp) { @@ -67,7 +75,12 @@ class FileSystemController { }) } - // POST: api/filesystem/pathexists + /** + * POST: /api/filesystem/pathexists + * + * @param {RequestWithUser} req + * @param {Response} res + */ async checkPathExists(req, res) { if (!req.user.canUpload) { Logger.error(`[FileSystemController] Non-admin user "${req.user.username}" attempting to check path exists`) diff --git a/server/controllers/LibraryController.js b/server/controllers/LibraryController.js index 57bec9f26f..48021a3047 100644 --- a/server/controllers/LibraryController.js +++ b/server/controllers/LibraryController.js @@ -1,3 +1,4 @@ +const { Request, Response, NextFunction } = require('express') const Sequelize = require('sequelize') const Path = require('path') const fs = require('../libs/fsExtra') @@ -22,9 +23,23 @@ const libraryFilters = require('../utils/queries/libraryFilters') const libraryItemsPodcastFilters = require('../utils/queries/libraryItemsPodcastFilters') const authorFilters = require('../utils/queries/authorFilters') +/** + * @typedef RequestUserObject + * @property {import('../models/User')} user + * + * @typedef {Request & RequestUserObject} RequestWithUser + */ + class LibraryController { constructor() {} + /** + * POST: /api/libraries + * Create a new library + * + * @param {RequestWithUser} req + * @param {Response} res + */ async create(req, res) { const newLibraryPayload = { ...req.body @@ -98,8 +113,8 @@ class LibraryController { /** * GET: /api/libraries/:id * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async findOne(req, res) { const includeArray = (req.query.include || '').split(',') @@ -121,8 +136,8 @@ class LibraryController { /** * GET: /api/libraries/:id/episode-downloads * Get podcast episodes in download queue - * @param {*} req - * @param {*} res + * @param {RequestWithUser} req + * @param {Response} res */ async getEpisodeDownloadQueue(req, res) { const libraryDownloadQueueDetails = this.podcastManager.getDownloadQueueDetails(req.library.id) @@ -132,8 +147,8 @@ class LibraryController { /** * PATCH: /api/libraries/:id * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async update(req, res) { /** @type {import('../objects/Library')} */ @@ -235,8 +250,9 @@ class LibraryController { /** * DELETE: /api/libraries/:id * Delete a library - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async delete(req, res) { const library = req.library @@ -298,8 +314,8 @@ class LibraryController { /** * GET /api/libraries/:id/items * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async getLibraryItems(req, res) { const include = (req.query.include || '') @@ -340,8 +356,8 @@ class LibraryController { /** * DELETE: /libraries/:id/issues * Remove all library items missing or invalid - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async removeLibraryItemsWithIssues(req, res) { const libraryItemsWithIssues = await Database.libraryItemModel.findAll({ @@ -398,8 +414,8 @@ class LibraryController { * GET: /api/libraries/:id/series * Optional query string: `?include=rssfeed` that adds `rssFeed` to series if a feed is open * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async getAllSeriesForLibrary(req, res) { const include = (req.query.include || '') @@ -434,8 +450,8 @@ class LibraryController { * rssfeed: adds `rssFeed` to series object if a feed is open * progress: adds `progress` to series object with { libraryItemIds:Array, libraryItemIdsFinished:Array, isFinished:boolean } * - * @param {import('express').Request} req - * @param {import('express').Response} res - Series + * @param {RequestWithUser} req + * @param {Response} res - Series */ async getSeriesForLibrary(req, res) { const include = (req.query.include || '') @@ -470,8 +486,9 @@ class LibraryController { /** * GET: /api/libraries/:id/collections * Get all collections for library - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getCollectionsForLibrary(req, res) { const include = (req.query.include || '') @@ -508,8 +525,9 @@ class LibraryController { /** * GET: /api/libraries/:id/playlists * Get playlists for user in library - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getUserPlaylistsForLibrary(req, res) { let playlistsForUser = await Database.playlistModel.getOldPlaylistsForUserAndLibrary(req.user.id, req.library.id) @@ -532,8 +550,9 @@ class LibraryController { /** * GET: /api/libraries/:id/filterdata - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getLibraryFilterData(req, res) { const filterData = await libraryFilters.getFilterData(req.library.mediaType, req.library.id) @@ -543,8 +562,9 @@ class LibraryController { /** * GET: /api/libraries/:id/personalized * Home page shelves - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getUserPersonalizedShelves(req, res) { const limitPerShelf = req.query.limit && !isNaN(req.query.limit) ? Number(req.query.limit) || 10 : 10 @@ -559,8 +579,9 @@ class LibraryController { /** * POST: /api/libraries/order * Change the display order of libraries - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async reorder(req, res) { if (!req.user.isAdminOrUp) { @@ -598,9 +619,10 @@ class LibraryController { /** * GET: /api/libraries/:id/search * Search library items with query + * * ?q=search - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async search(req, res) { if (!req.query.q || typeof req.query.q !== 'string') { @@ -616,8 +638,9 @@ class LibraryController { /** * GET: /api/libraries/:id/stats * Get stats for library - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async stats(req, res) { const stats = { @@ -658,8 +681,9 @@ class LibraryController { /** * GET: /api/libraries/:id/authors * Get authors for library - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getAuthors(req, res) { const { bookWhere, replacements } = libraryItemsBookFilters.getUserPermissionBookWhereQuery(req.user) @@ -696,8 +720,9 @@ class LibraryController { /** * GET: /api/libraries/:id/narrators - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getNarrators(req, res) { // Get all books with narrators @@ -742,8 +767,9 @@ class LibraryController { * Update narrator name * :narratorId is base64 encoded name * req.body { name } - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async updateNarrator(req, res) { if (!req.user.canUpdate) { @@ -792,8 +818,9 @@ class LibraryController { * DELETE: /api/libraries/:id/narrators/:narratorId * Remove narrator * :narratorId is base64 encoded name - * @param {*} req - * @param {*} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async removeNarrator(req, res) { if (!req.user.canUpdate) { @@ -835,8 +862,8 @@ class LibraryController { * GET: /api/libraries/:id/matchall * Quick match all library items. Book libraries only. * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async matchAll(req, res) { if (!req.user.isAdminOrUp) { @@ -852,8 +879,8 @@ class LibraryController { * Optional query: * ?force=1 * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async scan(req, res) { if (!req.user.isAdminOrUp) { @@ -872,8 +899,9 @@ class LibraryController { /** * GET: /api/libraries/:id/recent-episodes * Used for latest page - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getRecentEpisodes(req, res) { if (!req.library.isPodcast) { @@ -894,8 +922,9 @@ class LibraryController { /** * GET: /api/libraries/:id/opml * Get OPML file for a podcast library - * @param {import('express').Request} req - * @param {import('express').Response} res + * + * @param {RequestWithUser} req + * @param {Response} res */ async getOPMLFile(req, res) { const userPermissionPodcastWhere = libraryItemsPodcastFilters.getUserPermissionPodcastWhereQuery(req.user) @@ -920,8 +949,8 @@ class LibraryController { /** * Remove all metadata.json or metadata.abs files in library item folders * - * @param {import('express').Request} req - * @param {import('express').Response} res + * @param {RequestWithUser} req + * @param {Response} res */ async removeAllMetadataFiles(req, res) { if (!req.user.isAdminOrUp) { @@ -968,10 +997,10 @@ class LibraryController { } /** - * Middleware that is not using libraryItems from memory - * @param {import('express').Request} req - * @param {import('express').Response} res - * @param {import('express').NextFunction} next + * + * @param {RequestWithUser} req + * @param {Response} res + * @param {NextFunction} next */ async middleware(req, res, next) { if (!req.user.checkCanAccessLibrary(req.params.id)) { diff --git a/server/controllers/LibraryItemController.js b/server/controllers/LibraryItemController.js index 7967c801d2..0ec2b49e73 100644 --- a/server/controllers/LibraryItemController.js +++ b/server/controllers/LibraryItemController.js @@ -17,10 +17,10 @@ const CoverManager = require('../managers/CoverManager') const ShareManager = require('../managers/ShareManager') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class LibraryItemController { diff --git a/server/controllers/MeController.js b/server/controllers/MeController.js index 905c728e13..c7abbc2327 100644 --- a/server/controllers/MeController.js +++ b/server/controllers/MeController.js @@ -7,10 +7,10 @@ const { toNumber, isNullOrNaN } = require('../utils/index') const userStats = require('../utils/queries/userStats') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class MeController { diff --git a/server/controllers/MiscController.js b/server/controllers/MiscController.js index 7093ab1f75..ac6afff727 100644 --- a/server/controllers/MiscController.js +++ b/server/controllers/MiscController.js @@ -15,10 +15,10 @@ const TaskManager = require('../managers/TaskManager') const adminStats = require('../utils/queries/adminStats') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class MiscController { diff --git a/server/controllers/NotificationController.js b/server/controllers/NotificationController.js index ff9fff27f0..215afe0ab1 100644 --- a/server/controllers/NotificationController.js +++ b/server/controllers/NotificationController.js @@ -3,10 +3,10 @@ const Database = require('../Database') const { version } = require('../../package.json') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class NotificationController { diff --git a/server/controllers/PlaylistController.js b/server/controllers/PlaylistController.js index 476db1222d..5b84fe16f5 100644 --- a/server/controllers/PlaylistController.js +++ b/server/controllers/PlaylistController.js @@ -6,10 +6,10 @@ const Database = require('../Database') const Playlist = require('../objects/Playlist') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class PlaylistController { diff --git a/server/controllers/PodcastController.js b/server/controllers/PodcastController.js index 032f372e42..30688c7687 100644 --- a/server/controllers/PodcastController.js +++ b/server/controllers/PodcastController.js @@ -15,10 +15,10 @@ const CoverManager = require('../managers/CoverManager') const LibraryItem = require('../objects/LibraryItem') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class PodcastController { @@ -129,7 +129,7 @@ class PodcastController { * @typedef getPodcastFeedReqBody * @property {string} rssFeed * - * @param {Request<{}, {}, getPodcastFeedReqBody, {}> & RequestUserObjects} req + * @param {Request<{}, {}, getPodcastFeedReqBody, {}> & RequestUserObject} req * @param {Response} res */ async getPodcastFeed(req, res) { diff --git a/server/controllers/RSSFeedController.js b/server/controllers/RSSFeedController.js index 4b243c6328..5c7cc2a044 100644 --- a/server/controllers/RSSFeedController.js +++ b/server/controllers/RSSFeedController.js @@ -4,10 +4,10 @@ const Database = require('../Database') const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class RSSFeedController { diff --git a/server/controllers/SearchController.js b/server/controllers/SearchController.js index a9fee2ab0c..cfe4e6d3ea 100644 --- a/server/controllers/SearchController.js +++ b/server/controllers/SearchController.js @@ -8,10 +8,10 @@ const Database = require('../Database') const { isValidASIN } = require('../utils') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class SearchController { diff --git a/server/controllers/SeriesController.js b/server/controllers/SeriesController.js index 5d0631296f..54b0453855 100644 --- a/server/controllers/SeriesController.js +++ b/server/controllers/SeriesController.js @@ -5,10 +5,10 @@ const Database = require('../Database') const libraryItemsBookFilters = require('../utils/queries/libraryItemsBookFilters') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class SeriesController { diff --git a/server/controllers/SessionController.js b/server/controllers/SessionController.js index 011aa95092..cc6c0fd729 100644 --- a/server/controllers/SessionController.js +++ b/server/controllers/SessionController.js @@ -6,10 +6,10 @@ const { toNumber, isUUID } = require('../utils/index') const ShareManager = require('../managers/ShareManager') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class SessionController { @@ -206,7 +206,7 @@ class SessionController { * @typedef batchDeleteReqBody * @property {string[]} sessions * - * @param {Request<{}, {}, batchDeleteReqBody, {}> & RequestUserObjects} req + * @param {Request<{}, {}, batchDeleteReqBody, {}> & RequestUserObject} req * @param {Response} res */ async batchDelete(req, res) { diff --git a/server/controllers/ShareController.js b/server/controllers/ShareController.js index 374acef2a9..e1568c0dbe 100644 --- a/server/controllers/ShareController.js +++ b/server/controllers/ShareController.js @@ -12,10 +12,10 @@ const PlaybackSession = require('../objects/PlaybackSession') const ShareManager = require('../managers/ShareManager') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class ShareController { diff --git a/server/controllers/ToolsController.js b/server/controllers/ToolsController.js index f3062c171e..32cd5a6c97 100644 --- a/server/controllers/ToolsController.js +++ b/server/controllers/ToolsController.js @@ -3,10 +3,10 @@ const Logger = require('../Logger') const Database = require('../Database') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser */ class ToolsController { diff --git a/server/controllers/UserController.js b/server/controllers/UserController.js index 777bddb88d..37caa61cc1 100644 --- a/server/controllers/UserController.js +++ b/server/controllers/UserController.js @@ -9,16 +9,15 @@ const User = require('../objects/user/User') const { toNumber } = require('../utils/index') /** - * @typedef RequestUserObjects + * @typedef RequestUserObject * @property {import('../models/User')} user * - * @typedef {Request & RequestUserObjects} RequestWithUser + * @typedef {Request & RequestUserObject} RequestWithUser * - * @typedef UserControllerRequestProps - * @property {import('../models/User')} user - User that made the request - * @property {import('../objects/user/User')} [reqUser] - User for req param id + * @typedef RequestEntityObject + * @property {import('../models/User')} reqUser * - * @typedef {Request & UserControllerRequestProps} UserControllerRequest + * @typedef {RequestWithUser & RequestEntityObject} UserControllerRequest */ class UserController { @@ -26,7 +25,7 @@ class UserController { /** * - * @param {UserControllerRequest} req + * @param {RequestWithUser} req * @param {Response} res */ async findAll(req, res) { @@ -100,7 +99,7 @@ class UserController { return oldMediaProgress }) - const userJson = req.reqUser.toJSONForBrowser(!req.user.isRoot) + const userJson = req.reqUser.toOldJSONForBrowser(!req.user.isRoot) userJson.mediaProgress = oldMediaProgresses @@ -122,7 +121,7 @@ class UserController { const usernameExists = await Database.userModel.checkUserExistsWithUsername(username) if (usernameExists) { - return res.status(500).send('Username already taken') + return res.status(400).send('Username already taken') } account.id = uuidv4() @@ -132,6 +131,7 @@ class UserController { account.createdAt = Date.now() const newUser = new User(account) + // TODO: Create with new User model const success = await Database.createUser(newUser) if (success) { SocketAuthority.adminEmitter('user_added', newUser.toJSONForBrowser()) @@ -147,6 +147,8 @@ class UserController { * PATCH: /api/users/:id * Update user * + * @this {import('../routers/ApiRouter')} + * * @param {UserControllerRequest} req * @param {Response} res */ @@ -158,12 +160,12 @@ class UserController { return res.sendStatus(403) } - var account = req.body - var shouldUpdateToken = false + const updatePayload = req.body + let shouldUpdateToken = false // When changing username create a new API token - if (account.username !== undefined && account.username !== user.username) { - const usernameExists = await Database.userModel.checkUserExistsWithUsername(account.username) + if (updatePayload.username !== undefined && updatePayload.username !== user.username) { + const usernameExists = await Database.userModel.checkUserExistsWithUsername(updatePayload.username) if (usernameExists) { return res.status(500).send('Username already taken') } @@ -171,23 +173,25 @@ class UserController { } // Updating password - if (account.password) { - account.pash = await this.auth.hashPass(account.password) - delete account.password + if (updatePayload.password) { + updatePayload.pash = await this.auth.hashPass(updatePayload.password) + delete updatePayload.password } - if (user.update(account)) { + // TODO: Update with new User model + const oldUser = Database.userModel.getOldUser(user) + if (oldUser.update(updatePayload)) { if (shouldUpdateToken) { - user.token = await this.auth.generateAccessToken(user) - Logger.info(`[UserController] User ${user.username} was generated a new api token`) + oldUser.token = await this.auth.generateAccessToken(oldUser) + Logger.info(`[UserController] User ${oldUser.username} has generated a new api token`) } - await Database.updateUser(user) - SocketAuthority.clientEmitter(req.user.id, 'user_updated', user.toJSONForBrowser()) + await Database.updateUser(oldUser) + SocketAuthority.clientEmitter(req.user.id, 'user_updated', oldUser.toJSONForBrowser()) } res.json({ success: true, - user: user.toJSONForBrowser() + user: oldUser.toJSONForBrowser() }) } @@ -221,8 +225,8 @@ class UserController { await playlist.destroy() } - const userJson = user.toJSONForBrowser() - await Database.removeUser(user.id) + const userJson = user.toOldJSONForBrowser() + await user.destroy() SocketAuthority.adminEmitter('user_removed', userJson) res.json({ success: true @@ -237,13 +241,16 @@ class UserController { */ async unlinkFromOpenID(req, res) { Logger.debug(`[UserController] Unlinking user "${req.reqUser.username}" from OpenID with sub "${req.reqUser.authOpenIDSub}"`) - req.reqUser.authOpenIDSub = null - if (await Database.userModel.updateFromOld(req.reqUser)) { - SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.reqUser.toJSONForBrowser()) - res.sendStatus(200) - } else { - res.sendStatus(500) + + if (!req.reqUser.authOpenIDSub) { + return res.sendStatus(200) } + + req.reqUser.extraData.authOpenIDSub = null + req.reqUser.changed('extraData', true) + await req.reqUser.save() + SocketAuthority.clientEmitter(req.user.id, 'user_updated', req.reqUser.toOldJSONForBrowser()) + res.sendStatus(200) } /** @@ -318,8 +325,7 @@ class UserController { } if (req.params.id) { - // TODO: Update to use new user model - req.reqUser = await Database.userModel.getOldUserById(req.params.id) + req.reqUser = await Database.userModel.getUserById(req.params.id) if (!req.reqUser) { return res.sendStatus(404) } diff --git a/server/models/User.js b/server/models/User.js index 9bd8caa8f2..04f04e2b82 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -217,14 +217,6 @@ class User extends Model { } } - static removeById(userId) { - return this.destroy({ - where: { - id: userId - } - }) - } - /** * Create root user * @param {string} username diff --git a/server/routers/ApiRouter.js b/server/routers/ApiRouter.js index 291c24d6c2..54cd97c094 100644 --- a/server/routers/ApiRouter.js +++ b/server/routers/ApiRouter.js @@ -56,6 +56,7 @@ class ApiRouter { this.cronManager = Server.cronManager /** @type {import('../managers/NotificationManager')} */ this.notificationManager = Server.notificationManager + /** @type {import('../managers/EmailManager')} */ this.emailManager = Server.emailManager this.apiCacheManager = Server.apiCacheManager