diff --git a/server/resources/nodeResource.ts b/server/resources/nodeResource.ts index 12a2a49..d8ac067 100644 --- a/server/resources/nodeResource.ts +++ b/server/resources/nodeResource.ts @@ -10,7 +10,7 @@ import {forConstraint, forConstraints} from "../validation/validator"; import * as Resources from "../utils/resources"; import {Entity} from "../utils/resources"; import {Request, Response} from "express"; -import {Node} from "../types"; +import {EnhancedNode, Node} from "../types"; const nodeFields = ['hostname', 'key', 'email', 'nickname', 'mac', 'coords', 'monitoring']; @@ -97,7 +97,7 @@ async function doGetAll(req: Request): Promise<{ total: number; pageNodes: any } const macs = _.map(realNodes, (node: Node): string => node.mac); const nodeStateByMac = await MonitoringService.getByMacs(macs); - const enhancedNodes: Entity[] = _.map(realNodes, (node: Node) => { + const enhancedNodes: EnhancedNode[] = _.map(realNodes, (node: Node): EnhancedNode => { const nodeState = nodeStateByMac[node.mac]; if (nodeState) { return deepExtend({}, node, { @@ -107,10 +107,10 @@ async function doGetAll(req: Request): Promise<{ total: number; pageNodes: any } }); } - return node; + return node as EnhancedNode; }); - const filteredNodes = Resources.filter( + const filteredNodes = Resources.filter( enhancedNodes, [ 'hostname', diff --git a/server/services/monitoringService.test.ts b/server/services/monitoringService.test.ts index 1edf951..6159071 100644 --- a/server/services/monitoringService.test.ts +++ b/server/services/monitoringService.test.ts @@ -1,6 +1,6 @@ import moment from 'moment'; import {ParsedNode, parseNode, parseNodesJson, parseTimestamp} from "./monitoringService"; -import {NodeState} from "../types"; +import {OnlineState} from "../types"; import Logger from '../logger'; import {MockLogger} from "../__mocks__/logger"; @@ -242,7 +242,7 @@ test('parseNode() should succeed parsing node without site and domain', () => { const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB", importTimestamp: importTimestamp, - state: NodeState.ONLINE, + state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), site: '', domain: '' @@ -274,7 +274,7 @@ test('parseNode() should succeed parsing node with site and domain', () => { const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB", importTimestamp: importTimestamp, - state: NodeState.ONLINE, + state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), site: 'test-site', domain: 'test-domain' @@ -463,7 +463,7 @@ test('parseNodesJson() should parse valid nodes', () => { const expectedParsedNode: ParsedNode = { mac: "12:34:56:78:90:AB", importTimestamp: parseTimestamp(TIMESTAMP_VALID_STRING), - state: NodeState.ONLINE, + state: OnlineState.ONLINE, lastSeen: parseTimestamp(TIMESTAMP_VALID_STRING), site: 'test-site', domain: 'test-domain' diff --git a/server/services/monitoringService.ts b/server/services/monitoringService.ts index efe86c6..c3f3184 100644 --- a/server/services/monitoringService.ts +++ b/server/services/monitoringService.ts @@ -16,7 +16,7 @@ import {normalizeMac} from "../utils/strings"; import {monitoringDisableUrl} from "../utils/urlBuilder"; import CONSTRAINTS from "../validation/constraints"; import {forConstraint} from "../validation/validator"; -import {MAC, MailType, Node, NodeId, NodeState, NodeStateData, UnixTimestampSeconds} from "../types"; +import {MAC, MailType, Node, NodeId, OnlineState, NodeStateData, UnixTimestampSeconds} from "../types"; const MONITORING_STATE_MACS_CHUNK_SIZE = 100; const NEVER_ONLINE_NODES_DELETION_CHUNK_SIZE = 20; @@ -37,7 +37,7 @@ const DELETE_OFFLINE_NODES_AFTER_DURATION: {amount: number, unit: unitOfTime.Dur export type ParsedNode = { mac: string, importTimestamp: Moment, - state: NodeState, + state: OnlineState, lastSeen: Moment, site: string, domain: string, @@ -212,7 +212,7 @@ export function parseNode(importTimestamp: Moment, nodeData: any): ParsedNode { return { mac: mac, importTimestamp: importTimestamp, - state: isOnline ? NodeState.ONLINE : NodeState.OFFLINE, + state: isOnline ? OnlineState.ONLINE : OnlineState.OFFLINE, lastSeen: lastSeen, site: site || '', domain: domain || '' @@ -520,7 +520,7 @@ async function retrieveNodeInformationForUrls(urls: string[]): Promise = (arg: unknown) => arg is T; export function isObject(arg: unknown): arg is object { return arg !== null && typeof arg === "object"; } -export function isArray(arg: unknown, isT: (arg: unknown) => arg is T): arg is Array { +export function isArray(arg: unknown, isT: TypeGuard): arg is Array { if (!Array.isArray(arg)) { return false; } @@ -22,12 +23,18 @@ export function isString(arg: unknown): arg is string { return typeof arg === "string" } +export function toIsArray(isT: TypeGuard): TypeGuard { + return (arg): arg is T[] => isArray(arg, isT); +} + +export function toIsEnum(enumDef: E): TypeGuard { + return (arg): arg is E => Object.values(enumDef).includes(arg as [keyof E]); +} + export type Version = string; -export function isVersion(arg: unknown): arg is Version { - // Should be good enough for now. - return typeof arg === "string"; -} +// Should be good enough for now. +export const isVersion = isString; export type NodeStatistics = { registered: number; @@ -229,3 +236,95 @@ export function isClientConfig(arg: unknown): arg is ClientConfig { typeof cfg.rootPath === "string" ); } + +// TODO: Token type. +export type Token = string; +export const isToken = isString; + +export type FastdKey = string; +export const isFastdKey = isString; + +export type MAC = string; +export const isMAC = isString; + +export type UnixTimestampSeconds = number; +export type UnixTimestampMilliseconds = number; + +export type MonitoringToken = string; +export enum MonitoringState { + ACTIVE = "active", + PENDING = "pending", + DISABLED = "disabled", +} + +export const isMonitoringState = toIsEnum(MonitoringState); + +export type NodeId = string; +export const isNodeId = isString; + +export interface Node { + token: Token; + nickname: string; + email: string; + hostname: string; + coords?: string; // TODO: Use object with longitude and latitude. + key?: FastdKey; + mac: MAC; + monitoring: boolean; + monitoringConfirmed: boolean; + monitoringState: MonitoringState; + modifiedAt: UnixTimestampSeconds; +} + +export function isNode(arg: unknown): arg is Node { + if (!isObject(arg)) { + return false; + } + const node = arg as Node; + // noinspection SuspiciousTypeOfGuard + return ( + isToken(node.token) && + typeof node.nickname === "string" && + typeof node.email === "string" && + typeof node.hostname === "string" && + (node.coords === undefined || typeof node.coords === "string") && + (node.key === undefined || isFastdKey(node.key)) && + isMAC(node.mac) && + typeof node.monitoring === "boolean" && + typeof node.monitoringConfirmed === "boolean" && + isMonitoringState(node.monitoringState) && + typeof node.modifiedAt === "number" + ); +} + +export enum OnlineState { + ONLINE = "ONLINE", + OFFLINE = "OFFLINE", +} +export const isOnlineState = toIsEnum(OnlineState); + +export type Site = string; +export const isSite = isString; + +export type Domain = string; +export const isDomain = isString; + +export interface EnhancedNode extends Node { + site?: Site, + domain?: Domain, + onlineState?: OnlineState, +} + +export function isEnhancedNode(arg: unknown): arg is EnhancedNode { + if (!isNode(arg)) { + return false; + } + const node = arg as EnhancedNode; + return ( + (node.site === undefined || isSite(node.site)) && + (node.domain === undefined || isDomain(node.domain)) && + (node.onlineState === undefined || isOnlineState(node.onlineState)) + ); +} + +export const isEnhancedNodes = toIsArray(isEnhancedNode); diff --git a/server/utils/resources.ts b/server/utils/resources.ts index 3a34fde..bc92fe4 100644 --- a/server/utils/resources.ts +++ b/server/utils/resources.ts @@ -152,7 +152,7 @@ export async function getValidRestParams( return restParams as RestParams; } -export function filter (entities: ArrayLike, allowedFilterFields: string[], restParams: RestParams) { +export function filter(entities: ArrayLike, allowedFilterFields: string[], restParams: RestParams): E[] { let query = restParams.q; if (query) { query = _.toLower(query.trim());