Merge branch 'main' into new-admin

This commit is contained in:
baldo 2022-07-22 17:19:13 +02:00
commit 6fd4dbb33e
7 changed files with 72 additions and 37 deletions

View file

@ -32,7 +32,7 @@ function getNormalizedNodeData(reqData: any): CreateOrUpdateNode {
_.each(nodeFields, function (field) { _.each(nodeFields, function (field) {
let value = normalizeString(reqData[field]); let value = normalizeString(reqData[field]);
if (field === 'mac') { if (field === 'mac') {
value = normalizeMac(value); value = normalizeMac(value as MAC);
} }
node[field] = value; node[field] = value;
}); });

View file

@ -63,7 +63,17 @@ export function toNodeTokenResponse(node: StoredNode): NodeTokenResponse {
export function toDomainSpecificNodeResponse(node: StoredNode, nodeStateData: NodeStateData): DomainSpecificNodeResponse { export function toDomainSpecificNodeResponse(node: StoredNode, nodeStateData: NodeStateData): DomainSpecificNodeResponse {
return { return {
...toNodeResponse(node), token: node.token,
nickname: node.nickname,
email: node.email,
hostname: node.hostname,
coords: node.coords,
key: node.key,
mac: node.mac,
monitoring: node.monitoringState !== MonitoringState.DISABLED,
monitoringConfirmed: node.monitoringState === MonitoringState.ACTIVE,
monitoringState: node.monitoringState,
modifiedAt: node.modifiedAt,
site: nodeStateData.site, site: nodeStateData.site,
domain: nodeStateData.domain, domain: nodeStateData.domain,
onlineState: nodeStateData.state, onlineState: nodeStateData.state,

View file

@ -93,6 +93,14 @@ export function isBoolean(arg: unknown): arg is boolean {
return typeof arg === "boolean" return typeof arg === "boolean"
} }
export function isUndefined(arg: unknown): arg is undefined {
return arg === undefined;
}
export function isNull(arg: unknown): arg is null {
return arg === null;
}
export function toIsArray<T>(isT: TypeGuard<T>): TypeGuard<T[]> { export function toIsArray<T>(isT: TypeGuard<T>): TypeGuard<T[]> {
return (arg): arg is T[] => isArray(arg, isT); return (arg): arg is T[] => isArray(arg, isT);
} }
@ -473,7 +481,7 @@ export const isDomain = isString;
/** /**
* Represents a node in the context of a Freifunk site and domain. * Represents a node in the context of a Freifunk site and domain.
*/ */
export type DomainSpecificNodeResponse = NodeResponse & { export type DomainSpecificNodeResponse = Record<NodeSortField, any> & NodeResponse & {
site?: Site, site?: Site,
domain?: Domain, domain?: Domain,
onlineState?: OnlineState, onlineState?: OnlineState,

View file

@ -9,7 +9,7 @@ import {
EnumTypeGuard, EnumTypeGuard,
EnumValue, EnumValue,
type GenericSortField, type GenericSortField,
isJSONObject, isJSONObject, isNumber, isString, isUndefined,
JSONObject, JSONObject,
SortDirection, SortDirection,
TypeGuard TypeGuard
@ -91,17 +91,14 @@ function filterCondition(restParams: RestParams, filterFields: string[]): Filter
}; };
} }
let query = _.join( let query = filterFields
_.map(filterFields, function (field) { .map(field => 'LOWER(' + field + ') LIKE ?')
return 'LOWER(' + field + ') LIKE ?'; .join(' OR ');
}),
' OR '
);
query += ' ESCAPE \'\\\''; query += ' ESCAPE \'\\\'';
const search = '%' + (_.isString(restParams.q) ? escapeForLikePattern(_.toLower(restParams.q.trim())) : '') + '%'; const search = '%' + (isString(restParams.q) ? escapeForLikePattern(restParams.q.trim().toLowerCase()) : '') + '%';
const params = _.times(filterFields.length, _.constant(search)); const params = _.times(filterFields.length, () => search);
return { return {
query: query, query: query,
@ -111,13 +108,14 @@ function filterCondition(restParams: RestParams, filterFields: string[]): Filter
function getConstrainedValues(data: { [key: string]: any }, constraints: Constraints): { [key: string]: any } { function getConstrainedValues(data: { [key: string]: any }, constraints: Constraints): { [key: string]: any } {
const values: { [key: string]: any } = {}; const values: { [key: string]: any } = {};
_.each(_.keys(constraints), (key: string): void => { for (const key of Object.keys(constraints)) {
const value = data[key]; const value = data[key];
values[key] = values[key] =
_.isUndefined(value) && key in constraints && !_.isUndefined(constraints[key].default) isUndefined(value) && key in constraints && !isUndefined(constraints[key].default)
? constraints[key].default ? constraints[key].default
: value; : value;
});
}
return values; return values;
} }
@ -176,10 +174,10 @@ export async function getValidRestParams(
return restParams as RestParams; return restParams as RestParams;
} }
export function filter<E>(entities: ArrayLike<E>, allowedFilterFields: string[], restParams: RestParams): E[] { export function filter<E>(entities: E[], allowedFilterFields: string[], restParams: RestParams): E[] {
let query = restParams.q; let query = restParams.q;
if (query) { if (query) {
query = _.toLower(query.trim()); query = query.trim().toLowerCase();
} }
function queryMatches(entity: Entity): boolean { function queryMatches(entity: Entity): boolean {
@ -191,15 +189,15 @@ export function filter<E>(entities: ArrayLike<E>, allowedFilterFields: string[],
return true; return true;
} }
let value = entity[field]; let value = entity[field];
if (_.isNumber(value)) { if (isNumber(value)) {
value = value.toString(); value = value.toString();
} }
if (!_.isString(value) || _.isEmpty(value)) { if (!isString(value) || _.isEmpty(value)) {
return false; return false;
} }
value = _.toLower(value); value = value.toLowerCase();
if (field === 'mac') { if (field === 'mac') {
return _.includes(value.replace(/:/g, ''), query.replace(/:/g, '')); return _.includes(value.replace(/:/g, ''), query.replace(/:/g, ''));
} }
@ -216,7 +214,7 @@ export function filter<E>(entities: ArrayLike<E>, allowedFilterFields: string[],
} }
return _.every(filters, (value: any, key: string): boolean => { return _.every(filters, (value: any, key: string): boolean => {
if (_.isUndefined(value)) { if (isUndefined(value)) {
return true; return true;
} }
if (_.startsWith(key, 'has')) { if (_.startsWith(key, 'has')) {
@ -232,25 +230,43 @@ export function filter<E>(entities: ArrayLike<E>, allowedFilterFields: string[],
}); });
} }
export function sort<T, S>(entities: ArrayLike<T>, isSortField: TypeGuard<S>, restParams: RestParams): ArrayLike<T> { export function sort<T extends Record<S, any>, S extends string>(entities: T[], isSortField: TypeGuard<S>, restParams: RestParams): T[] {
const sortField: S | undefined = isSortField(restParams._sortField) ? restParams._sortField : undefined; const sortField: S | undefined = isSortField(restParams._sortField) ? restParams._sortField : undefined;
if (!sortField) { if (!sortField) {
return entities; return entities;
} }
const sorted: T[] = _.sortBy(entities, [sortField]); const sorted = entities.slice(0);
if (restParams._sortDir === SortDirection.ASCENDING) { sorted.sort((a, b) => {
return sorted; let as = a[sortField];
} else { let bs = b[sortField];
return _.reverse(sorted);
if (isString(as)) {
as = as.toLowerCase();
} }
if (isString(bs)) {
bs = bs.toLowerCase();
} }
export function getPageEntities(entities: ArrayLike<Entity>, restParams: RestParams) { let order = 0;
if (as < bs) {
order = -1;
}
else if (bs > as) {
order = 1;
}
return restParams._sortDir === SortDirection.DESCENDING ? -order : order;
});
return sorted;
}
export function getPageEntities(entities: Entity[], restParams: RestParams) {
const page = restParams._page; const page = restParams._page;
const perPage = restParams._perPage; const perPage = restParams._perPage;
return _.slice(entities, (page - 1) * perPage, page * perPage); return entities.slice((page - 1) * perPage, page * perPage);
} }
export {filterCondition as whereCondition}; export {filterCondition as whereCondition};

View file

@ -1,12 +1,13 @@
import _ from "lodash" import _ from "lodash"
import {MAC} from "../types";
export function normalizeString(str: string): string { export function normalizeString(str: string): string {
return _.isString(str) ? str.trim().replace(/\s+/g, ' ') : str; return _.isString(str) ? str.trim().replace(/\s+/g, ' ') : str;
} }
export function normalizeMac (mac: string): string { export function normalizeMac(mac: MAC): MAC {
// parts only contains values at odd indexes // parts only contains values at odd indexes
const parts = mac.toUpperCase().replace(/:/g, '').split(/([A-F0-9]{2})/); const parts = mac.toUpperCase().replace(/[-:]/g, '').split(/([A-F0-9]{2})/);
const macParts = []; const macParts = [];
@ -14,7 +15,7 @@ export function normalizeMac (mac: string): string {
macParts.push(parts[i]); macParts.push(parts[i]);
} }
return macParts.join(':'); return macParts.join(':') as MAC;
} }
export function parseInteger(str: string): number { export function parseInteger(str: string): number {

View file

@ -40,7 +40,7 @@ const CONSTRAINTS = {
}, },
coords: { coords: {
type: 'string', type: 'string',
regex: /^(-?[0-9]{1,3}(\.[0-9]{1,15})? -?[0-9]{1,3}(\.[0-9]{1,15})?)$/, regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2}|([a-f0-9]{2}-){5}[a-f0-9]{2})$/i,
optional: true optional: true
}, },
monitoring: { monitoring: {

View file

@ -38,7 +38,7 @@
}, },
mac: { mac: {
type: 'string', type: 'string',
regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2})$/i, regex: /^([a-f0-9]{12}|([a-f0-9]{2}:){5}[a-f0-9]{2}|([a-f0-9]{2}-){5}[a-f0-9]{2})$/i,
optional: false optional: false
}, },
coords: { coords: {