diff --git a/server/routes/api/torrents.ts b/server/routes/api/torrents.ts index 57411acf..2939991a 100644 --- a/server/routes/api/torrents.ts +++ b/server/routes/api/torrents.ts @@ -12,6 +12,7 @@ import type { AddTorrentByFileOptions, AddTorrentByURLOptions, ContentToken, + ReannounceTorrentsOptions, SetTorrentsTagsOptions, } from '@shared/schema/api/torrents'; import type { @@ -31,6 +32,7 @@ import type { import { addTorrentByFileSchema, addTorrentByURLSchema, + reannounceTorrentsSchema, setTorrentsTagsSchema, } from '../../../shared/schema/api/torrents'; import {accessDeniedError, fileNotFoundError, isAllowedPath, sanitizePath} from '../../util/fileUtil'; @@ -436,10 +438,38 @@ router.post( ), ); +/** + * POST /api/torrents/reannounce + * @summary Reannounces torrents to trackers + * @tags Torrents + * @security User + * @param {ReannounceTorrentsOptions} - request.body.required - options - application/json + * @return {object} 200 - success response - application/json + * @return {Error} 500 - failure response - application/json + */ +router.post( + '/reannounce', + async (req, res): Promise => { + const parsedResult = reannounceTorrentsSchema.safeParse(req.body); + + if (!parsedResult.success) { + return res.status(422).json({message: 'Validation error.'}); + } + + return req.services.clientGatewayService.reannounceTorrents(parsedResult.data).then( + (response) => { + req.services.clientGatewayService.fetchTorrentList(); + return res.status(200).json(response); + }, + ({code, message}) => res.status(500).json({code, message}), + ); + }, +); + /** * PATCH /api/torrents/initial-seeding * @summary Sets initial seeding mode of torrents. - * @tags Torrent + * @tags Torrents * @security User * @param {SetTorrentsInitialSeedingOptions} request.body.required - options - application/json * @return {object} 200 - success response - application/json @@ -460,7 +490,7 @@ router.patch( /** * PATCH /api/torrents/priority * @summary Sets priority of torrents. - * @tags Torrent + * @tags Torrents * @security User * @param {SetTorrentsPriorityOptions} request.body.required - options - application/json * @return {object} 200 - success response - application/json @@ -481,7 +511,7 @@ router.patch( /** * PATCH /api/torrents/sequential * @summary Sets sequential mode of torrents. - * @tags Torrent + * @tags Torrents * @security User * @param {SetTorrentsSequentialOptions} request.body.required - options - application/json * @return {object} 200 - success response - application/json diff --git a/server/services/interfaces/clientGatewayService.ts b/server/services/interfaces/clientGatewayService.ts index a8357345..0dbeafe0 100644 --- a/server/services/interfaces/clientGatewayService.ts +++ b/server/services/interfaces/clientGatewayService.ts @@ -1,6 +1,7 @@ import type { AddTorrentByFileOptions, AddTorrentByURLOptions, + ReannounceTorrentsOptions, SetTorrentsTagsOptions, } from '@shared/schema/api/torrents'; import type { @@ -93,6 +94,14 @@ abstract class ClientGatewayService extends BaseService; + /** + * Reannounces torrents to trackers + * + * @param {ReannounceTorrentsOptions} options - An object of options... + * @return {Promise} - Rejects with error. + */ + abstract reannounceTorrents({hashes}: ReannounceTorrentsOptions): Promise; + /** * Removes torrents. Optionally deletes data of torrents. * diff --git a/shared/schema/api/torrents.ts b/shared/schema/api/torrents.ts index c5b1e802..66982b98 100644 --- a/shared/schema/api/torrents.ts +++ b/shared/schema/api/torrents.ts @@ -63,6 +63,14 @@ export const setTorrentsTagsSchema = object({ export type SetTorrentsTagsOptions = zodInfer; +// POST /api/torrents/reannounce +export const reannounceTorrentsSchema = object({ + // An array of string representing hashes of torrents to be reannounced + hashes: array(string()).nonempty(), +}); + +export type ReannounceTorrentsOptions = zodInfer; + // GET /api/torrents/{hash}/contents/{indices}/data export const contentTokenSchema = object({ username: string(),