diff --git a/client/src/javascript/actions/SettingsActions.ts b/client/src/javascript/actions/SettingsActions.ts index 729ee1af..d80387dd 100644 --- a/client/src/javascript/actions/SettingsActions.ts +++ b/client/src/javascript/actions/SettingsActions.ts @@ -31,7 +31,7 @@ const SettingsActions = { modifyFeed: (id: string, options: ModifyFeedOptions) => axios - .put(`${baseURI}api/feed-monitor/feeds/${id}`, options) + .patch(`${baseURI}api/feed-monitor/feeds/${id}`, options) .then((json) => json.data) .then( () => { diff --git a/client/src/javascript/components/modals/feeds-modal/DownloadRulesTab.tsx b/client/src/javascript/components/modals/feeds-modal/DownloadRulesTab.tsx index 683abc2a..00080287 100644 --- a/client/src/javascript/components/modals/feeds-modal/DownloadRulesTab.tsx +++ b/client/src/javascript/components/modals/feeds-modal/DownloadRulesTab.tsx @@ -31,8 +31,9 @@ import * as validators from '../../../util/validators'; type ValidatedFields = 'destination' | 'feedID' | 'label' | 'match' | 'exclude'; -interface RuleFormData extends Omit { +interface RuleFormData extends Omit { check: string; + feedID: string; tags: string; } @@ -81,7 +82,7 @@ const MESSAGES = defineMessages({ const defaultRule = { label: '', - feedID: '', + feedIDs: [''], match: '', exclude: '', tags: [], @@ -153,11 +154,15 @@ class DownloadRulesTab extends React.Component + defaultID={rule.feedIDs?.[0]}> {this.getAvailableFeedsOptions()} @@ -473,7 +478,7 @@ class DownloadRulesTab extends React.Component { const fieldName = field as ValidatedFields; - const fieldValue = formData[fieldName]; + const fieldValue = fieldName === 'feedID' ? formData.feedIDs[0] : formData[fieldName]; if (!this.validatedFields[fieldName].isValid(fieldValue) && accumulator != null) { accumulator[fieldName] = this.validatedFields[fieldName].error; diff --git a/client/src/javascript/components/modals/feeds-modal/FeedsTab.tsx b/client/src/javascript/components/modals/feeds-modal/FeedsTab.tsx index 2f7d047a..8a3a4959 100644 --- a/client/src/javascript/components/modals/feeds-modal/FeedsTab.tsx +++ b/client/src/javascript/components/modals/feeds-modal/FeedsTab.tsx @@ -497,7 +497,7 @@ class FeedsTab extends React.Component { // TODO: Properly handle array of array of URLs const torrentsToDownload = this.props.items .filter((_item, index) => formData[index]) - .map((item, index) => ({id: index, value: item.torrentURLs[0]})); + .map((item, index) => ({id: index, value: item.urls[0]})); UIActions.displayModal({id: 'add-torrents', initialURLs: torrentsToDownload}); }; diff --git a/server/routes/api/feed-monitor.ts b/server/routes/api/feed-monitor.ts index 01fb0033..8a8f04de 100644 --- a/server/routes/api/feed-monitor.ts +++ b/server/routes/api/feed-monitor.ts @@ -93,7 +93,7 @@ router.put('/feeds', (req, res) => { }); /** - * PUT /api/feed-monitor/feeds/{id} + * PATCH /api/feed-monitor/feeds/{id} * @summary Modifies the options of a feed subscription * @tags Feeds * @security User @@ -102,7 +102,7 @@ router.put('/feeds', (req, res) => { * @return {} 200 - success response - application/json * @return {Error} 500 - failure response - application/json */ -router.put<{id: string}, unknown, ModifyFeedOptions>('/feeds/:id', (req, res) => { +router.patch<{id: string}, unknown, ModifyFeedOptions>('/feeds/:id', (req, res) => { const callback = ajaxUtil.getResponseFn(res); req.services?.feedService diff --git a/server/services/feedService.ts b/server/services/feedService.ts index 790ea207..cf9e30db 100644 --- a/server/services/feedService.ts +++ b/server/services/feedService.ts @@ -111,14 +111,14 @@ class FeedService extends BaseService { }); }); - if (this.rules[newRule.feedID] == null) { - this.rules[newRule.feedID] = []; + if (this.rules[newRule.feedIDs[0]] == null) { + this.rules[newRule.feedIDs[0]] = []; } - this.rules[newRule.feedID].push(newRule); + this.rules[newRule.feedIDs[0]].push(newRule); const associatedFeedReader = this.feedReaders.find( - (feedReader) => feedReader.getOptions().feedID === newRule.feedID, + (feedReader) => feedReader.getOptions().feedID === newRule.feedIDs[0], ); if (associatedFeedReader) { @@ -194,7 +194,7 @@ class FeedService extends BaseService { return filteredItems.map((item) => { return { title: typeof item.title === 'string' ? item.title : 'Unknown', - torrentURLs: getTorrentUrlsFromFeedItem(item), + urls: getTorrentUrlsFromFeedItem(item), }; }); } @@ -309,11 +309,34 @@ class FeedService extends BaseService { // Add all download rules to the local state. feedsSummary.rules.forEach((rule) => { - if (this.rules[rule.feedID] == null) { - this.rules[rule.feedID] = []; + const feedID = rule?.feedIDs?.[0]; + + // Migration + if (feedID == null) { + this.removeItem(rule._id).then(() => { + const oldFeedID = ((rule as unknown) as {feedID: string})?.feedID; + if (oldFeedID != null) { + this.addRule({ + destination: rule.destination, + tags: rule.tags, + label: rule.label, + feedIDs: [oldFeedID], + field: rule.field, + match: rule.match, + exclude: rule.exclude, + startOnLoad: rule.startOnLoad, + isBasePath: rule.isBasePath, + }); + } + }); + return; } - this.rules[rule.feedID].push(rule); + if (this.rules[feedID] == null) { + this.rules[feedID] = []; + } + + this.rules[feedID].push(rule); }); // Initiate all feeds. diff --git a/shared/types/Feed.ts b/shared/types/Feed.ts index e8a9e4db..d871ef29 100644 --- a/shared/types/Feed.ts +++ b/shared/types/Feed.ts @@ -1,34 +1,54 @@ export interface Feed { type: 'feed'; + // Unique ID of the feed, generated by server by the time the feed is added. _id: string; + // User-facing label (name) of the feed. label: string; + // URL of the feed. url: string; + // Interval between checking the feed for new items. interval: number; + // How many times rules have matched items of the feed. count?: number; } export interface Rule { type: 'rule'; + // Unique ID of the rule, generated by server by the time the automation is added. _id: string; + // User-facing label (name) of the rule. label: string; - feedID: string; + // IDs of feeds of which this rule should apply to. + // TODO: Multi feed support not implemented. Only feedIDs[0] is used at the moment. + feedIDs: Array; + // Field of the feed item to be matched with rules. [default: 'title'] field?: string; + // Regular expression to match items. match: string; + // Regular expression to exclude items. exclude: string; + // Destination path where matched items are downloaded to. destination: string; + // Tags to be added when items are queued for download. tags: Array; + // Start to download immediately after the items are queued. startOnLoad: boolean; + // Whether the destination path should be used as base path. isBasePath?: boolean; + // How many items this rule has matched. count?: number; } export interface MatchedTorrents { type: 'matchedTorrents'; _id: string; + // Previously queued URLs from matched feed items. So we don't queue them again. urls: Array; } export interface Item { + // Title of the feed item. title: string; - torrentURLs: Array; + // URLs extracted from the feed item. + urls: Array; }