server: services: migrate to TypeScript

This commit is contained in:
Jesse Chan
2020-09-22 22:42:50 +08:00
parent dcbe26b940
commit 43b2d8fbbf
138 changed files with 3570 additions and 3238 deletions
@@ -1,6 +1,6 @@
const objectUtil = require('../util/objectUtil');
import objectUtil from '../util/objectUtil';
const clientSettings = {
export const clientSettingsMap = {
dht: 'dht.mode',
dhtPort: 'dht.port',
dhtStats: 'dht.statistics',
@@ -48,8 +48,13 @@ const clientSettings = {
throttleMinPeersSeed: 'throttle.min_peers.seed',
trackersNumWant: 'trackers.numwant',
trackersUseUdp: 'trackers.use_udp',
} as const;
// TODO: Is this bidirectional map really necessary?
export const clientSettingsBiMap = objectUtil.reflect(clientSettingsMap);
export type ClientSetting = keyof typeof clientSettingsMap;
export type ClientSettings = {
// TODO: Need proper types for each property
[property in ClientSetting]?: string | Record<string, unknown> | null;
};
const clientSettingsMap = objectUtil.reflect(clientSettings);
module.exports = {clientSettings, clientSettingsMap};
-7
View File
@@ -1,7 +0,0 @@
const diffActionTypes = ['ITEM_ADDED', 'ITEM_CHANGED', 'ITEM_REMOVED'];
module.exports = diffActionTypes.reduce((memo, key) => {
memo[key] = key;
return memo;
}, {});
+18
View File
@@ -0,0 +1,18 @@
import objectUtil from '../util/objectUtil';
const diffActionTypes = ['ITEM_ADDED', 'ITEM_CHANGED', 'ITEM_REMOVED'] as const;
export default objectUtil.createStringMapFromArray(diffActionTypes);
export type DiffActionType = typeof diffActionTypes[number];
export type DiffAction<T = unknown> = Array<
| {
action: Exclude<DiffActionType, 'ITEM_REMOVED'>;
data: T;
}
| {
action: 'ITEM_REMOVED';
data: keyof T;
}
>;
-12
View File
@@ -1,12 +0,0 @@
const objectUtil = require('../util/objectUtil');
const historySnapshotTypes = {
FIVE_MINUTE: 'fiveMin',
THIRTY_MINUTE: 'thirtyMin',
HOUR: 'hour',
WEEK: 'week',
MONTH: 'month',
YEAR: 'year',
};
module.exports = objectUtil.reflect(historySnapshotTypes);
+4
View File
@@ -0,0 +1,4 @@
const historySnapshotTypes = ['FIVE_MINUTE', 'THIRTY_MINUTE', 'HOUR', 'DAY', 'WEEK', 'MONTH', 'YEAR'] as const;
export default historySnapshotTypes;
export type HistorySnapshot = typeof historySnapshotTypes[number];
-19
View File
@@ -1,19 +0,0 @@
const objectUtil = require('../util/objectUtil');
const serverEventTypes = [
'CLIENT_CONNECTIVITY_STATUS_CHANGE',
'DISK_USAGE_CHANGE',
'NOTIFICATION_COUNT_CHANGE',
'TAXONOMY_FULL_UPDATE',
'TAXONOMY_DIFF_CHANGE',
'TORRENT_LIST_ACTION_TORRENT_ADDED',
'TORRENT_LIST_ACTION_TORRENT_DELETED',
'TORRENT_LIST_ACTION_TORRENT_DETAIL_UPDATED',
'TORRENT_LIST_DIFF_CHANGE',
'TORRENT_LIST_FULL_UPDATE',
'TRANSFER_HISTORY_FULL_UPDATE',
'TRANSFER_SUMMARY_DIFF_CHANGE',
'TRANSFER_SUMMARY_FULL_UPDATE',
];
module.exports = objectUtil.createStringMapFromArray(serverEventTypes);
@@ -1,6 +1,6 @@
const torrentFilePropsMap = {
props: ['path', 'pathComponents', 'priority', 'sizeBytes', 'sizeChunks', 'completedChunks'],
methods: ['f.path=', 'f.path_components=', 'f.priority=', 'f.size_bytes=', 'f.size_chunks=', 'f.completed_chunks='],
};
} as const;
module.exports = torrentFilePropsMap;
export default torrentFilePropsMap;
@@ -27,6 +27,6 @@ const torrentPeerPropsMap = {
'p.is_encrypted=',
'p.is_incoming=',
],
};
} as const;
module.exports = torrentPeerPropsMap;
export default torrentPeerPropsMap;
-19
View File
@@ -1,19 +0,0 @@
const objectUtil = require('../util/objectUtil');
const torrentStatusMap = objectUtil.reflect({
ch: 'checking',
sd: 'seeding',
p: 'paused',
c: 'complete',
d: 'downloading',
ad: 'activelyDownloading',
au: 'activelyUploading',
s: 'stopped',
e: 'error',
i: 'inactive',
a: 'active',
});
torrentStatusMap.statusShorthand = ['ch', 'sd', 'p', 'c', 'd', 'ad', 'au', 's', 'e', 'i', 'a'];
module.exports = torrentStatusMap;
+16
View File
@@ -0,0 +1,16 @@
const torrentStatusMap = [
'checking',
'seeding',
'paused',
'complete',
'downloading',
'activelyDownloading',
'activelyUploading',
'stopped',
'error',
'inactive',
'active',
] as const;
export type TorrentStatus = typeof torrentStatusMap[number] | 'all';
export default torrentStatusMap;
@@ -1,6 +1,6 @@
const torrentTrackerPropsMap = {
props: ['group', 'url', 'id', 'minInterval', 'normalInterval', 'type'],
methods: ['t.group=', 't.url=', 't.id=', 't.min_interval=', 't.normal_interval=', 't.type='],
};
} as const;
module.exports = torrentTrackerPropsMap;
export default torrentTrackerPropsMap;
+16
View File
@@ -0,0 +1,16 @@
export interface AddTorrentByURLOptions {
urls: Array<string>;
destination: string;
isBasePath: boolean;
start: boolean;
tags?: Array<string>;
}
export interface MoveTorrentsOptions {
destination: string;
isBasePath: boolean;
filenames: Array<string>;
sourcePaths: Array<string>;
moveFiles: boolean;
isCheckHash: boolean;
}
+2
View File
@@ -14,6 +14,8 @@ export interface Credentials {
isAdmin?: boolean;
}
export type UserInDatabase = Required<Credentials> & {_id: string};
// auth/authenticate
export interface AuthAuthenticationResponse {
success: boolean;
-9
View File
@@ -1,9 +0,0 @@
// TODO: Unite with clientSettingsMap when server is TS.
import {clientSettings} from '../constants/clientSettingsMap';
export type ClientSetting = keyof typeof clientSettings;
export type ClientSettings = {
// TODO: Need proper types for each property
[property in ClientSetting]?: string | Record<string, unknown> | null;
};
+8
View File
@@ -0,0 +1,8 @@
export interface Disk {
target: string;
size: number;
avail: number;
used: number;
}
export type Disks = Array<Disk>;
+28
View File
@@ -0,0 +1,28 @@
export interface Notification {
_id?: string;
id: 'notification.torrent.finished' | 'notification.torrent.errored' | 'notification.feed.downloaded.torrent';
read: boolean;
ts: number; // timestamp
data: {
name: string;
ruleLabel?: string;
feedLabel?: string;
title?: string;
};
}
export interface NotificationCount {
total: number;
unread: number;
read: number;
}
export interface NotificationState {
id: string;
count: NotificationCount;
limit: number;
start: number;
notifications: Array<Notification>;
}
export type NotificationFetchOptions = Pick<NotificationState, 'id' | 'limit' | 'start'> & {allNotifications?: boolean};
+21
View File
@@ -0,0 +1,21 @@
import type {Disks} from './DiskUsage';
import type {NotificationCount} from './Notification';
import type {Taxonomy, TaxonomyDiffs} from './Taxonomy';
import type {Torrents, TorrentListDiff} from './Torrent';
import type {TransferHistory, TransferSummary, TransferSummaryDiff} from './TransferData';
// type: data
export interface ServerEvents {
CLIENT_CONNECTIVITY_STATUS_CHANGE: {
isConnected: boolean;
};
DISK_USAGE_CHANGE: Disks;
NOTIFICATION_COUNT_CHANGE: NotificationCount;
TAXONOMY_FULL_UPDATE: Taxonomy;
TAXONOMY_DIFF_CHANGE: TaxonomyDiffs;
TORRENT_LIST_FULL_UPDATE: Torrents;
TORRENT_LIST_DIFF_CHANGE: TorrentListDiff;
TRANSFER_HISTORY_FULL_UPDATE: TransferHistory;
TRANSFER_SUMMARY_FULL_UPDATE: TransferSummary;
TRANSFER_SUMMARY_DIFF_CHANGE: TransferSummaryDiff;
}
+15
View File
@@ -0,0 +1,15 @@
import type {DiffAction} from '../constants/diffActionTypes';
export interface Taxonomy {
statusCounts: Record<string, number>;
tagCounts: Record<string, number>;
trackerCounts: Record<string, number>;
}
type TaxonomyDiff<T extends keyof Taxonomy> = DiffAction<Taxonomy[T]> | null;
export interface TaxonomyDiffs {
statusCounts: TaxonomyDiff<'statusCounts'>;
tagCounts: TaxonomyDiff<'tagCounts'>;
trackerCounts: TaxonomyDiff<'trackerCounts'>;
}
+117
View File
@@ -0,0 +1,117 @@
import {TorrentStatus} from '../constants/torrentStatusMap';
export interface Duration {
weeks?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
cumSeconds: number;
}
export interface TorrentDetails {
fileTree: {
files: Array<{
index: number;
filename: string;
path: string;
percentComplete: number;
priority: number;
sizeBytes: number;
}>;
peers: Array<TorrentPeer>;
trackers: Array<TorrentTracker>;
};
}
// TODO: Unite with torrentPeerPropsMap when it is TS.
export interface TorrentPeer {
index: number;
country: string;
address: string;
completedPercent: number;
clientVersion: string;
downloadRate: number;
downloadTotal: number;
uploadRate: number;
uploadTotal: number;
id: string;
peerRate: number;
peerTotal: number;
isEncrypted: boolean;
isIncoming: boolean;
}
// TODO: Unite with torrentTrackerPropsMap when it is TS.
export interface TorrentTracker {
index: number;
id: string;
url: string;
type: number;
group: number;
minInterval: number;
normalInterval: number;
}
// TODO: Rampant over-fetching of torrent properties. Need to remove unused items.
// TODO: Unite with torrentListPropMap when it is TS.
export interface TorrentProperties {
baseDirectory: string;
baseFilename: string;
basePath: string;
bytesDone: number;
comment: string;
dateAdded: string;
dateCreated: string;
details: TorrentDetails;
directory: string;
downRate: number;
downTotal: number;
eta: 'Infinity' | Duration;
hash: string;
ignoreScheduler: boolean;
isActive: boolean;
isComplete: boolean;
isHashing: string;
isMultiFile: boolean;
isOpen: boolean;
isPrivate: boolean;
isStateChanged: boolean;
message: string;
name: string;
peersConnected: number;
peersTotal: number;
percentComplete: number;
priority: string;
ratio: number;
seedingTime: string;
seedsConnected: number;
seedsTotal: number;
sizeBytes: number;
state: string;
status: Array<TorrentStatus>;
tags: Array<string>;
throttleName: string;
trackerURIs: Array<string>;
upRate: number;
upTotal: number;
}
export interface TorrentListDiff {
[hash: string]:
| {
action: 'TORRENT_LIST_ACTION_TORRENT_ADDED';
data: TorrentProperties;
}
| {
action: 'TORRENT_LIST_ACTION_TORRENT_DELETED';
}
| {
action: 'TORRENT_LIST_ACTION_TORRENT_DETAIL_UPDATED';
data: Partial<TorrentProperties>;
};
}
export interface Torrents {
[hash: string]: TorrentProperties;
}
+23
View File
@@ -0,0 +1,23 @@
import {DiffAction} from '@shared/constants/diffActionTypes';
export interface TransferSummary {
downRate: number;
downThrottle: number;
downTotal: number;
upRate: number;
upThrottle: number;
upTotal: number;
}
export type TransferSummaryDiff = DiffAction<Partial<TransferSummary>>;
export type TransferDirection = 'upload' | 'download';
export type TransferData = Record<TransferDirection, number>;
export interface TransferSnapshot extends TransferData {
numUpdates?: number;
timestamp: number;
}
export type TransferHistory = Record<TransferDirection | 'timestamps', Array<number>>;
-72
View File
@@ -1,72 +0,0 @@
const diffActionTypes = require('../constants/diffActionTypes');
const objectUtil = {
createStringMapFromArray: (array) =>
array.reduce((memo, key) => {
memo[key] = key;
return memo;
}, {}),
createSymbolMapFromArray: (array = []) =>
array.reduce((memo, key) => {
memo[key] = Symbol(key);
return memo;
}, {}),
getDiff: (prevObject = {}, nextObject = {}) => {
const prevObjectKeys = Object.keys(prevObject);
const nextObjectKeys = Object.keys(nextObject);
let shouldCheckForRemovals = nextObjectKeys.length < prevObjectKeys.length;
const diff = nextObjectKeys.reduce((accumulator, key) => {
const prevValue = prevObject[key];
const nextValue = nextObject[key];
if (prevValue == null) {
shouldCheckForRemovals = true;
accumulator.push({
action: diffActionTypes.ITEM_ADDED,
data: {
[key]: nextValue,
},
});
} else if (prevValue !== nextValue) {
accumulator.push({
action: diffActionTypes.ITEM_CHANGED,
data: {
[key]: nextValue,
},
});
}
return accumulator;
}, []);
if (shouldCheckForRemovals) {
prevObjectKeys.forEach((key) => {
if (nextObject[key] == null) {
diff.push({
action: diffActionTypes.ITEM_REMOVED,
data: key,
});
}
});
}
return diff;
},
reflect: (object) =>
Object.keys(object).reduce((memo, key) => {
memo[key] = object[key];
memo[object[key]] = key;
return memo;
}, {}),
};
module.exports = objectUtil;
+88
View File
@@ -0,0 +1,88 @@
import type {DiffAction} from '../constants/diffActionTypes';
type KeyFromValue<V, T extends Record<string, string>> = {
[K in keyof T]: V extends T[K] ? K : never;
}[keyof T];
const objectUtil = {
createStringMapFromArray: <T extends string>(array: Readonly<Array<T>>): Readonly<{[key in T]: key}> => {
return array.reduce((memo, key) => {
return Object.assign(memo, {
[key]: key,
});
}, {} as Partial<{[key in T]: key}>) as Readonly<{[key in T]: key}>;
},
getDiff: <P extends string, N extends string>(
prevObject: Partial<Record<P, unknown>>,
nextObject: Partial<Record<N, unknown>>,
) => {
const prevObjectKeys = Object.keys(prevObject);
const nextObjectKeys = Object.keys(nextObject);
let shouldCheckForRemovals = nextObjectKeys.length < prevObjectKeys.length;
const diff = nextObjectKeys.reduce((accumulator, key) => {
const prevValue = prevObject[key as P];
const nextValue = nextObject[key as N];
if (prevValue == null) {
shouldCheckForRemovals = true;
accumulator.push({
action: 'ITEM_ADDED',
data: {
[key]: nextValue,
},
} as {
action: 'ITEM_ADDED';
data: {
[key in N]: typeof nextObject[key];
};
});
} else if (prevValue !== nextValue) {
accumulator.push({
action: 'ITEM_CHANGED',
data: {
[key]: nextValue,
},
} as {
action: 'ITEM_CHANGED';
data: {
[key in N]: typeof nextObject[key];
};
});
}
return accumulator;
}, [] as DiffAction<Record<N, unknown>>);
if (shouldCheckForRemovals) {
prevObjectKeys.forEach((key) => {
if (nextObject[key as N] == null) {
diff.push({
action: 'ITEM_REMOVED',
data: key as N,
});
}
});
}
return diff;
},
reflect: <T extends Record<K, string>, K extends keyof T>(
object: T,
): {[value in T[K]]: KeyFromValue<value, T>} & {[key in K]: T[key]} => {
return Object.assign(
object,
Object.keys(object).reduce((memo, key) => {
return Object.assign(memo, {
[object[key as K]]: key,
});
}, {} as {[value in T[K]]: KeyFromValue<value, T>}),
);
},
};
export default objectUtil;
@@ -2,6 +2,6 @@ const regEx = {
url: /^(?:https?|ftp):\/\/.{1,}\.{1}.{1,}/,
domainName: /(?:https?|udp):\/\/(?:www\.)?([-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,18}\b)*(\/[/\d\w.-]*)*(?:[?])*(.+)*/i,
cdata: /<!\[CDATA\[(.*?)\]\]>/,
};
} as const;
module.exports = regEx;
export default regEx;
-16
View File
@@ -1,16 +0,0 @@
module.exports = {
capitalize: (string) => string.charAt(0).toUpperCase() + string.slice(1),
pluralize: (string, count) => {
if (count !== 1) {
if (string.charAt(string.length - 1) === 'y') {
return `${string.substring(0, string.length - 1)}ies`;
}
return `${string}s`;
}
return string;
},
withoutTrailingSlash: (input) => input.replace(/\/{1,}$/, ''),
};
+4
View File
@@ -0,0 +1,4 @@
export default {
capitalize: (string: string): string => string.charAt(0).toUpperCase() + string.slice(1),
withoutTrailingSlash: (input: string): string => input.replace(/\/{1,}$/, ''),
};