import { RouterOSAPI , RosException } from "node-routeros" ; import { flatten , reduce } from "lodash" ; import * as utils from "./utils" ; import * as Types from "./Types" ; export abstract class RouterOSAPICrud { protected rosApi : RouterOSAPI ; protected pathVal : string ; protected proplistVal : string ; protected queryVal : string [ ] = [ ] ; protected snakeCase : boolean ; private needsObjectTranslation : boolean = false ; private placeAfter : any ; param rosApi param path param snakeCase constructor ( rosApi , path , snakeCase ) { this . rosApi = rosApi ; this . snakeCase = snakeCase ; this . pathVal = path . replace ( / /g , "/" ) . replace ( /(print|enable|disable|add|set|remove|getall|move)$/ , "" ) . replace ( /\/$/ , "" ) ; } /**
     * Get the current menu
     */ public getCurrentMenu ( ) { return this . pathVal ; } param data public add ( data ) { return this . exec ( "add" , data ) . then ( ( results ) => { if ( results . length === 0 ) return Promise . resolve ( null ) ; return this . recoverDataFromChangedItems ( results . shift ( ) . ret ) ; } ) ; } param data public create ( data ) { return this . add ( data ) ; } param ids public disable ( ids ? ) { if ( ids ) { ids = this . stringfySearchQuery ( ids ) ; this . queryVal . push ( "=numbers=" + ids ) ; } let disabledIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . queryForIdsIfNeeded ( disabledIds ) . then ( ( ids ) => { disabledIds = ids ; return this . exec ( "disable" ) ; } ) . then ( ( response ) => { return this . recoverDataFromChangedItems ( disabledIds ) ; } ) ; } param ids public delete ( ids ? ) { return this . remove ( ids ) ; } param ids public enable ( ids ? ) { if ( ids ) { ids = this . stringfySearchQuery ( ids ) ; this . queryVal . push ( "=numbers=" + ids ) ; } let enabledIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . queryForIdsIfNeeded ( enabledIds ) . then ( ( ids ) => { enabledIds = ids ; return this . exec ( "enable" ) ; } ) . then ( ( response ) => { return this . recoverDataFromChangedItems ( enabledIds ) ; } ) ; } param command param data public exec ( command , data ? ) { if ( data ) this . makeQuery ( data ) ; const query = this . fullQuery ( "/" + command ) ; return this . translateQueryIntoId ( query ) . then ( ( consultedQuery ) => { return this . write ( consultedQuery ) ; } ) . then ( ( results ) => { return this . prepareToPlaceAfter ( results ) ; } ) ; } param to public moveAbove ( to ? ) { let movedIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . queryForIdsIfNeeded ( movedIds ) . then ( ( ids ) => { movedIds = ids ; if ( to ) this . queryVal . push ( "=destination=" + to ) ; return this . exec ( "move" ) ; } ) . then ( ( ) => { return this . recoverDataFromChangedItems ( movedIds ) ; } ) ; } param data param ids public update ( data , ids ? ) { if ( ids ) { ids = this . stringfySearchQuery ( ids ) ; this . queryVal . push ( "=numbers=" + ids ) ; } let updatedIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . queryForIdsIfNeeded ( updatedIds ) . then ( ( ids ) => { updatedIds = ids ; this . makeQuery ( data ) ; return this . exec ( "set" ) ; } ) . then ( ( response ) => { return this . recoverDataFromChangedItems ( updatedIds ) ; } ) ; } param properties param ids public unset ( properties , ids ? ) { if ( ids ) { ids = this . stringfySearchQuery ( ids ) ; this . queryVal . push ( "=numbers=" + ids ) ; } let updatedIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . queryForIdsIfNeeded ( updatedIds ) . then ( ( ids ) => { updatedIds = ids ; if ( typeof properties === "string" ) properties = [ properties ] ; const $q = [ ] ; const curQueryVal = this . queryVal . slice ( ) ; this . queryVal = [ ] ; properties . forEach ( ( property ) => { this . queryVal = curQueryVal . slice ( ) ; this . queryVal . push ( "=value-name=" + utils . camelCaseOrSnakeCaseToDashedCase ( property ) ) ; $q . push ( this . exec ( "unset" ) ) ; } ) ; return Promise . all ( $q ) ; } ) . then ( ( ) => { return this . recoverDataFromChangedItems ( updatedIds ) ; } ) ; } param ids public remove ( ids ? ) { if ( ids ) { ids = this . stringfySearchQuery ( ids ) ; this . queryVal . push ( "=numbers=" + ids ) ; } const idsForRemoval = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; let responseData ; return this . queryForIdsIfNeeded ( idsForRemoval ) . then ( ( ids ) => { return this . recoverDataFromChangedItems ( ids ) ; } ) . then ( ( response ) => { responseData = response ; return this . exec ( "remove" ) ; } ) . then ( ( ) => { return Promise . resolve ( responseData ) ; } ) ; } param data param ids public set ( data , ids ? ) { return this . update ( data , ids ) ; } param data param ids public edit ( data , ids ? ) { return this . update ( data , ids ) ; } param from param to protected moveEntry ( from , to ? ) { if ( ! Array . isArray ( from ) ) from = [ from ] ; from = this . stringfySearchQuery ( from ) ; this . queryVal . push ( "=numbers=" + from ) ; if ( to ) { to = this . stringfySearchQuery ( to ) ; this . queryVal . push ( "=destination=" + to ) ; } const movedIds = utils . lookForIdParameterAndReturnItsValue ( this . queryVal ) ; return this . exec ( "move" ) . then ( ( ) => { return this . recoverDataFromChangedItems ( movedIds ) ; } ) ; } param append protected fullQuery ( append ? ) { let val = [ ] ; if ( append ) { val . push ( this . pathVal + append ) ; } else { val . push ( this . pathVal ) ; } if ( this . proplistVal ) val . push ( this . proplistVal ) ; val = val . concat ( this . queryVal ) . slice ( ) ; if ( ! /(print|getall)$/ . test ( val [ 0 ] ) ) { for ( let index = 0 ; index < val . length ; index ++ ) { val [ index ] = val [ index ] . replace ( /^\?/ , "=" ) ; } } return val ; } param searchParameters protected makeQuery ( searchParameters , addQuestionMark = false , addToLocalQuery = true ) { let tmpKey ; let tmpVal ; const tmpQuery = addToLocalQuery ? this . queryVal : [ ] ; for ( const key in searchParameters ) { if ( searchParameters . hasOwnProperty ( key ) ) { tmpVal = searchParameters [ key ] ; if ( /[A-Z]/ . test ( tmpKey ) ) { tmpKey = tmpKey . replace ( /([A-Z])/g , "$1" ) . toLowerCase ( ) ; } tmpKey = key . replace ( /_/ , "-" ) ; switch ( tmpKey ) { case "id" : tmpKey = ".id" ; break ; case "next" : tmpKey = ".nextid" ; break ; case "dead" : tmpKey = ".dead" ; break ; default : break ; } if ( typeof tmpVal === "boolean" ) { tmpVal = tmpVal ? "yes" : "no" ; } else if ( tmpVal === null ) { tmpVal = "" ; } else if ( typeof tmpVal === "object" ) { tmpVal = this . stringfySearchQuery ( tmpVal ) ; } else if ( tmpKey === "placeAfter" ) { this . placeAfter = tmpVal ; tmpKey = "placeBefore" ; } tmpKey = ( addQuestionMark ? "?" : "=" ) + tmpKey ; tmpKey = utils . camelCaseOrSnakeCaseToDashedCase ( tmpKey ) ; tmpQuery . push ( tmpKey + "=" + tmpVal ) ; } } return tmpQuery ; } param query protected write ( query ) { this . queryVal = [ ] ; this . proplistVal = "" ; return this . rosApi . write ( query ) . then ( ( results ) => { return Promise . resolve ( this . treatMikrotikProperties ( results ) ) ; } ) ; } param queries protected translateQueryIntoId ( queries ) { if ( queries . length === 0 || ! this . needsObjectTranslation ) return Promise . resolve ( queries ) ; const promises = [ ] ; const consultedIndexes = [ ] ; for ( const [ index , element ] of queries . entries ( ) ) { const str = element . replace ( /^\?/ , "" ) . replace ( /^\=/ , "" ) ; if ( str . includes ( ".id=" ) || str . includes ( "place-before=" ) || str . includes ( "place-after=" ) || str . includes ( "numbers=" ) ) { if ( /\{.*\}/ . test ( str ) ) { const key = str . split ( "=" ) . shift ( ) ; const value = JSON . parse ( str . split ( "=" ) . pop ( ) ) ; const treatedQuery = [ this . pathVal + "/print" , "=.proplist=.id" ] . concat ( this . makeQuery ( value , true , false ) ) ; const promise = this . rosApi . write ( treatedQuery ) ; consultedIndexes . push ( { index : index , key : key } ) ; promises . push ( promise ) ; } } } return Promise . all ( promises ) . then ( ( results ) => { for ( let result of results ) { if ( Array . isArray ( result ) ) result = result . shift ( ) ; const consulted = consultedIndexes . shift ( ) ; if ( ! result ) return Promise . reject ( new RosException ( "REFNOTFND" , { key : consulted . key } ) ) ; if ( consulted . key === "place-after" ) { this . placeAfter = result [ ".id" ] ; consulted . key = "place-before" ; } queries [ consulted . index ] = "=" + consulted . key + "=" + result [ ".id" ] ; } this . needsObjectTranslation = false ; return Promise . resolve ( queries ) ; } ) ; } param results protected prepareToPlaceAfter ( results ) { if ( ! this . placeAfter || results . length !== 1 ) return Promise . resolve ( results ) ; if ( ! results [ 0 ] . ret ) return Promise . resolve ( results ) ; const from = this . placeAfter ; const to = results [ 0 ] . ret ; this . placeAfter = null ; return this . moveEntry ( from , to ) . then ( ( ) => { return Promise . resolve ( results ) ; } ) ; } param results protected treatMikrotikProperties ( results ) { const treatedArr = [ ] ; results . forEach ( ( result ) => { const tmpItem = { $$path : this . pathVal } ; for ( const key in result ) { if ( result . hasOwnProperty ( key ) ) { const tmpVal = result [ key ] ; let tmpKey = this . snakeCase ? utils . dashedCaseToSnakeCase ( key ) : utils . dashedCaseToCamelCase ( key ) ; tmpKey = tmpKey . replace ( /^\./ , "" ) ; tmpItem [ tmpKey ] = tmpVal ; if ( tmpVal === "true" || tmpVal === "false" ) { tmpItem [ tmpKey ] = tmpVal === "true" ; } else if ( /^\d+(\.\d+)?$/ . test ( tmpVal ) ) { tmpItem [ tmpKey ] = parseFloat ( tmpVal ) ; } } } treatedArr . push ( tmpItem ) ; } ) ; return treatedArr ; } param items private stringfySearchQuery ( items ) { let isArray = true ; const newItems = [ ] ; if ( ! Array . isArray ( items ) ) { isArray = false ; items = [ items ] ; } for ( const item of items ) { if ( typeof item === "object" ) { this . needsObjectTranslation = true ; newItems . push ( JSON . stringify ( item ) ) ; } else newItems . push ( item ) ; } return isArray ? newItems : newItems . shift ( ) ; } param data param ids private recoverDataFromChangedItems ( ids ? ) { if ( ! ids ) { return this . rosApi . write ( [ this . pathVal + "/print" ] ) . then ( ( data ) => Promise . resolve ( this . treatMikrotikProperties ( data ) . shift ( ) ) ) ; } const promises = [ ] ; const splittedIds = ids . split ( "," ) ; for ( const id of splittedIds ) { const promise = this . rosApi . write ( [ this . pathVal + "/print" , "?.id=" + id ] ) ; promises . push ( promise ) ; } return Promise . all ( promises ) . then ( ( data ) => { if ( ! data ) return Promise . resolve ( data ) ; data = flatten ( data ) ; data = this . treatMikrotikProperties ( data ) ; if ( ! ids . includes ( "," ) ) return Promise . resolve ( data . shift ( ) ) ; return Promise . resolve ( data ) ; } ) ; } param ids private queryForIdsIfNeeded ( ids ? ) { if ( ids ) return Promise . resolve ( ids ) ; this . queryVal . push ( "=.proplist=.id" ) ; const query = this . fullQuery ( "/print" ) ; let queriedIds ; return this . write ( query ) . then ( ( data ) => { data = reduce ( data , ( result , value , key ) => { result . push ( value . id ) ; return result ; } , [ ] ) ; queriedIds = data + "" ; if ( queriedIds ) this . queryVal . push ( "=numbers=" + queriedIds ) ; return Promise . resolve ( queriedIds ) ; } ) ; } }