server: migrate Flood settings functions to per-user service

This commit is contained in:
Jesse Chan
2020-10-12 21:23:52 +08:00
parent 8f5fd27b88
commit 98666ea51e
8 changed files with 199 additions and 127 deletions
@@ -32,6 +32,7 @@ const TorrentActions = {
type: 'CLIENT_ADD_TORRENT_SUCCESS',
data: {
count: options.urls.length,
start: options.start,
destination: options.destination,
response,
},
@@ -57,6 +58,7 @@ const TorrentActions = {
type: 'CLIENT_ADD_TORRENT_SUCCESS',
data: {
count: options.files.length,
start: options.start,
destination: options.destination,
data,
},
@@ -79,7 +81,6 @@ const TorrentActions = {
type: 'CLIENT_ADD_TORRENT_SUCCESS',
data: {
count: 1,
destination: '',
},
});
download(response.data, (options.name || `${Date.now()}`).concat('.torrent'));
@@ -233,7 +233,7 @@ interface ClientFetchTorrentDetailsSuccessAction {
interface ClientAddTorrentSuccessAction {
type: 'CLIENT_ADD_TORRENT_SUCCESS';
data: {count: number; destination: string};
data: {count: number; start?: boolean; destination?: string};
}
interface ClientMoveTorrentsSuccessAction {
+16 -3
View File
@@ -99,15 +99,28 @@ class TorrentStoreClass extends BaseStore {
this.emit('CLIENT_ADD_TORRENT_ERROR');
}
handleAddTorrentSuccess(response: {count: number; destination: string}) {
handleAddTorrentSuccess({count, start, destination}: {count: number; start?: boolean; destination?: string}) {
this.emit('CLIENT_ADD_TORRENT_SUCCESS');
SettingsStore.setFloodSetting('torrentDestination', response.destination);
// Remembers user's selections
const changedSettings: Partial<FloodSettings> = {
...(start != null
? {
startTorrentsOnLoad: start,
}
: {}),
...(destination != null
? {
torrentDestination: destination,
}
: {}),
};
SettingsStore.saveFloodSettings(changedSettings);
AlertStore.add({
accumulation: {
id: 'alert.torrent.add',
value: response.count || 1,
value: count || 1,
},
id: 'alert.torrent.add',
});
-95
View File
@@ -1,95 +0,0 @@
import Datastore from 'nedb';
import noop from 'lodash/noop';
import path from 'path';
import type {UserInDatabase} from '@shared/schema/Auth';
import config from '../../config';
interface SettingsRecord {
id: string;
data: unknown;
}
const databases = new Map();
function getDb(user: UserInDatabase): Datastore<SettingsRecord> {
const userId = user._id;
if (databases.has(userId)) {
return databases.get(userId);
}
const database = new Datastore<SettingsRecord>({
autoload: true,
filename: path.join(config.dbPath, userId, 'settings', 'settings.db'),
});
databases.set(userId, database);
return database;
}
const settings = {
get: (
user: UserInDatabase,
opts: {property?: string},
callback: (data: Record<string, unknown> | null, error?: Error) => void,
) => {
const query: {id?: string} = {};
const settingsToReturn: Record<string, unknown> = {};
if (opts.property) {
query.id = opts.property;
}
getDb(user)
.find(query)
.exec((err, docs) => {
if (err) {
callback(null, err);
return;
}
docs.forEach((doc) => {
settingsToReturn[doc.id] = doc.data;
});
callback(settingsToReturn);
});
},
set: (
user: UserInDatabase,
payloads: Array<SettingsRecord>,
callback: (data?: Array<Array<SettingsRecord>> | null, error?: Error) => void = noop,
) => {
const docsResponse: Array<Array<SettingsRecord>> = [];
if (payloads && payloads.length) {
let error;
payloads.forEach((payload) => {
getDb(user).update(
{id: payload.id},
{$set: {data: payload.data}},
{upsert: true},
(err: Error | null, _numberOfUpdated: number, docs: Array<SettingsRecord>, _upsert: boolean) => {
docsResponse.push(docs);
if (err) {
error = err;
}
},
);
});
if (error) {
callback(null, error);
} else {
callback(docsResponse);
}
} else {
callback();
}
},
};
export default settings;
+60 -20
View File
@@ -1,6 +1,7 @@
import express from 'express';
import passport from 'passport';
import type {FloodSettings} from '@shared/types/FloodSettings';
import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
import type {NotificationFetchOptions} from '@shared/types/Notification';
import type {SetFloodSettingsOptions} from '@shared/types/api/index';
@@ -13,7 +14,6 @@ import clientActivityStream from '../../middleware/clientActivityStream';
import eventStream from '../../middleware/eventStream';
import feedMonitorRoutes from './feed-monitor';
import {getDirectoryList} from '../../util/fileUtil';
import settings from '../../models/settings';
import torrentsRoutes from './torrents';
const router = express.Router();
@@ -54,29 +54,69 @@ router.delete('/notifications', (req, res) => {
req.services?.notificationService.clearNotifications(ajaxUtil.getResponseFn(res));
});
/**
* GET /api/settings
* @summary Gets all Flood's settings
* @tags Flood
* @security User
* @return {FloodSettings} 200 - success response - application/json
* @return {Error} 500 - failure response - application/json
*/
router.get('/settings', (req, res) => {
if (req.user == null) {
res.status(401).json(Error('Unauthorized'));
return;
}
settings.get(req.user, req.query, ajaxUtil.getResponseFn(res));
const callback = ajaxUtil.getResponseFn(res);
req.services?.settingService
.get(null)
.then((settings) => {
callback(settings as FloodSettings);
})
.catch((err) => {
callback(null, err);
});
});
/**
* GET /api/settings/{property}
* @summary Gets Flood's settings
* @tags Flood
* @security User
* @param property.path
* @return {Partial<FloodSettings>} 200 - success response - application/json
* @return {Error} 500 - failure response - application/json
*/
router.get('/settings/:property', (req, res) => {
const callback = ajaxUtil.getResponseFn(res);
req.services?.settingService
.get(req.params.property)
.then((settings) => {
callback(settings);
})
.catch((err) => {
callback(null, err);
});
});
/**
* PATCH /api/settings
* @summary Sets Flood's settings
* @tags Flood
* @security User
* @param {Partial<FloodSettings>} request.body.required - options - application/json
* @return {Partial<FloodSettings>} 200 - success response - application/json
* @return {Error} 500 - failure response - application/json
*/
router.patch<unknown, unknown, SetFloodSettingsOptions>('/settings', (req, res) => {
Promise.all(
Object.keys(req.body).map(async (property) => {
return {
id: property,
data: req.body[property as keyof typeof req.body],
};
}),
).then((settingRecords) => {
if (req.user == null) {
res.status(401).json(Error('Unauthorized'));
return;
}
settings.set(req.user, settingRecords, ajaxUtil.getResponseFn(res));
});
const callback = ajaxUtil.getResponseFn(res);
req.services?.settingService
.set(req.body)
.then((savedSettings) => {
callback(savedSettings);
})
.catch((err) => {
callback(null, err);
});
});
export default router;
-7
View File
@@ -23,7 +23,6 @@ import ajaxUtil from '../../util/ajaxUtil';
import client from '../../models/client';
import {getTempPath} from '../../models/TemporaryStorage';
import mediainfo from '../../util/mediainfo';
import settings from '../../models/settings';
const router = express.Router();
@@ -42,9 +41,6 @@ router.post<unknown, unknown, AddTorrentByURLOptions>('/add-urls', (req, res) =>
req.services?.clientGatewayService
.addTorrentsByURL(req.body)
.then((response) => {
if (req.user != null) {
settings.set(req.user, [{id: 'startTorrentsOnLoad', data: req.body.start === true}]);
}
req.services?.torrentService.fetchTorrentList();
return response;
})
@@ -69,9 +65,6 @@ router.post<unknown, unknown, AddTorrentByFileOptions>('/add-files', (req, res)
req.services?.clientGatewayService
.addTorrentsByFile(req.body)
.then((response) => {
if (req.user != null) {
settings.set(req.user, [{id: 'startTorrentsOnLoad', data: req.body.start === true}]);
}
req.services?.torrentService.fetchTorrentList();
return response;
})
+13
View File
@@ -5,6 +5,7 @@ import ClientRequestManager from './rTorrent/clientRequestManager';
import FeedService from './feedService';
import HistoryService from './historyService';
import NotificationService from './notificationService';
import SettingService from './settingService';
import TaxonomyService from './taxonomyService';
import TorrentService from './torrentService';
@@ -14,6 +15,7 @@ type Service =
| typeof FeedService
| typeof HistoryService
| typeof NotificationService
| typeof SettingService
| typeof TaxonomyService
| typeof TorrentService;
@@ -23,6 +25,7 @@ const serviceInstances: {
feedServices: Record<string, FeedService>;
historyServices: Record<string, HistoryService>;
notificationServices: Record<string, NotificationService>;
settingServices: Record<string, SettingService>;
taxonomyServices: Record<string, TaxonomyService>;
torrentServices: Record<string, TorrentService>;
} = {
@@ -31,6 +34,7 @@ const serviceInstances: {
feedServices: {},
historyServices: {},
notificationServices: {},
settingServices: {},
taxonomyServices: {},
torrentServices: {},
};
@@ -70,6 +74,10 @@ const getNotificationService = (user: UserInDatabase): NotificationService => {
return getService('notificationServices', NotificationService, user);
};
const getSettingService = (user: UserInDatabase): SettingService => {
return getService('settingServices', SettingService, user);
};
const getTaxonomyService = (user: UserInDatabase): TaxonomyService => {
return getService('taxonomyServices', TaxonomyService, user);
};
@@ -100,6 +108,10 @@ const getAllServices = (user: UserInDatabase) =>
return getNotificationService(user);
},
get settingService() {
return getSettingService(user);
},
get taxonomyService() {
return getTaxonomyService(user);
},
@@ -157,6 +169,7 @@ export default {
getClientGatewayService,
getHistoryService,
getNotificationService,
getSettingService,
getTaxonomyService,
getTorrentService,
};
+107
View File
@@ -0,0 +1,107 @@
import Datastore from 'nedb';
import path from 'path';
import type {FloodSettings} from '@shared/types/FloodSettings';
import config from '../../config';
import BaseService from './BaseService';
interface SettingRecord {
id: string;
data: unknown;
}
interface SettingServiceEvents {
SETTINGS_CHANGE: (changeSettings: Partial<FloodSettings>) => void;
}
class SettingService extends BaseService<SettingServiceEvents> {
db = this.loadDatabase();
loadDatabase(): Datastore<SettingRecord> {
const userId = this.user._id;
const database = new Datastore<SettingRecord>({
autoload: true,
filename: path.join(config.dbPath, userId, 'settings', 'settings.db'),
});
return database;
}
async get(property: string | null): Promise<Partial<FloodSettings>> {
return new Promise((resolve, reject) => {
this.db
.find(
property
? {
id: property,
}
: {},
)
.exec(async (err, docs) => {
if (err) {
reject(err);
}
resolve(
Object.assign(
{},
...(await Promise.all(
docs.map(async (doc) => {
return {
[doc.id]: doc.data,
};
}),
)),
),
);
});
});
}
async set(changedSettings: Partial<FloodSettings>): Promise<Partial<FloodSettings>> {
const savedSettings: typeof changedSettings = {};
if (changedSettings) {
let error: Error | null = null;
await Promise.all(
Object.keys(changedSettings).map((key) => {
return new Promise<keyof FloodSettings>((resolve, reject) => {
const property = key as keyof FloodSettings;
return this.db.update(
{id: property},
{$set: {data: changedSettings[property]}},
{upsert: true},
(err: Error | null) => {
if (err) {
reject(err);
return;
}
resolve(property);
},
);
})
.then((property) => {
Object.assign(savedSettings, {
[property]: changedSettings[property],
});
})
.catch((e) => {
error = e;
});
}),
);
if (error) {
return Promise.reject(error);
}
}
this.emit('SETTINGS_CHANGE', savedSettings);
return savedSettings;
}
}
export default SettingService;