2017-06-21 00:14:14 +00:00
'use strict' ;
2018-11-18 14:38:52 +00:00
const knex = require ( './knex' ) ;
2018-08-03 11:35:55 +00:00
const entitySettings = require ( './entity-settings' ) ;
2019-01-04 20:31:01 +00:00
const { enforce } = require ( './helpers' ) ;
const shares = require ( '../models/shares' ) ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
async function ajaxListTx ( tx , params , queryFun , columns , options ) {
2017-08-11 06:51:30 +00:00
options = options || { } ;
2017-08-13 18:11:58 +00:00
const columnsNames = [ ] ;
const columnsSelect = [ ] ;
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
for ( const col of columns ) {
if ( typeof col === 'string' ) {
columnsNames . push ( col ) ;
columnsSelect . push ( col ) ;
} else {
columnsNames . push ( col . name ) ;
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
if ( col . raw ) {
2018-12-28 19:54:00 +00:00
columnsSelect . push ( tx . raw ( col . raw , col . data || [ ] ) ) ;
2017-08-13 18:11:58 +00:00
} else if ( col . query ) {
columnsSelect . push ( function ( ) { return col . query ( this ) ; } ) ;
2017-07-26 19:42:05 +00:00
}
}
2017-08-13 18:11:58 +00:00
}
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
if ( params . operation === 'getBy' ) {
const query = queryFun ( tx ) ;
query . whereIn ( columnsNames [ parseInt ( params . column ) ] , params . values ) ;
query . select ( columnsSelect ) ;
2018-12-30 23:58:17 +00:00
query . options ( { nestTables : '.' } ) ;
2017-08-13 18:11:58 +00:00
const rows = await query ;
2018-12-30 23:58:17 +00:00
2017-08-13 18:11:58 +00:00
const rowsOfArray = rows . map ( row => Object . keys ( row ) . map ( key => row [ key ] ) ) ;
return rowsOfArray ;
} else {
const whereFun = function ( ) {
let searchVal = '%' + params . search . value . replace ( /\\/g , '\\\\' ) . replace ( /([%_])/g , '\\$1' ) + '%' ;
for ( let colIdx = 0 ; colIdx < params . columns . length ; colIdx ++ ) {
const col = params . columns [ colIdx ] ;
if ( col . searchable ) {
this . orWhere ( columnsNames [ parseInt ( col . data ) ] , 'like' , searchVal ) ;
2017-06-21 00:14:14 +00:00
}
2017-07-26 19:42:05 +00:00
}
2017-08-13 18:11:58 +00:00
}
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
/ * T h e r e a r e a f e w S Q L p e c u l i a r i t i e s t h a t m a k e t h i s q u e r y a b i t w e i r d :
- Group by ( which is used in getting permissions ) don ' t go well with count ( * ) . Thus we run the actual query
as a sub - query and then count the number of results .
- SQL does not like if it have columns with the same name in the subquery . This happens multiple tables are joined .
To circumvent this , we select only the first column ( whatever it is ) . Since this is not "distinct" , it is supposed
to give us the right number of rows anyway .
* /
const recordsTotalQuery = tx . count ( '* as recordsTotal' ) . from ( function ( ) { return queryFun ( this ) . select ( columnsSelect [ 0 ] ) . as ( 'records' ) ; } ) . first ( ) ;
const recordsTotal = ( await recordsTotalQuery ) . recordsTotal ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
const recordsFilteredQuery = tx . count ( '* as recordsFiltered' ) . from ( function ( ) { return queryFun ( this ) . select ( columnsSelect [ 0 ] ) . where ( whereFun ) . as ( 'records' ) ; } ) . first ( ) ;
const recordsFiltered = ( await recordsFilteredQuery ) . recordsFiltered ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
const query = queryFun ( tx ) ;
query . where ( whereFun ) ;
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
query . offset ( parseInt ( params . start ) ) ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
const limit = parseInt ( params . length ) ;
if ( limit >= 0 ) {
query . limit ( limit ) ;
}
2017-06-21 00:14:14 +00:00
2017-08-22 06:15:13 +00:00
query . select ( [ ... columnsSelect , ... options . extraColumns || [ ] ] ) ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
for ( const order of params . order ) {
if ( options . orderByBuilder ) {
options . orderByBuilder ( query , columnsNames [ params . columns [ order . column ] . data ] , order . dir ) ;
} else {
query . orderBy ( columnsNames [ params . columns [ order . column ] . data ] , order . dir ) ;
2017-07-10 15:37:56 +00:00
}
2017-08-13 18:11:58 +00:00
}
2017-06-21 00:14:14 +00:00
2018-12-30 23:58:17 +00:00
query . options ( { nestTables : '.' } ) ;
2017-07-11 09:28:44 +00:00
2017-08-13 18:11:58 +00:00
const rows = await query ;
2018-12-30 23:58:17 +00:00
// Here we rely on the fact that Object.keys(row) returns the columns in the same order as they are given in the select (i.e. in columnsNames).
// This should work because ES2015 guarantees chronological order of keys in an object and mysql (https://github.com/mysqljs/mysql/blob/ad014c82b2cbaf47acae1cc39e5533d3cb6eb882/lib/protocol/packets/RowDataPacket.js#L43)
// adds them in the order of select columns.
2017-08-13 18:11:58 +00:00
const rowsOfArray = rows . map ( row => {
const arr = Object . keys ( row ) . map ( field => row [ field ] ) ;
2017-07-26 19:42:05 +00:00
2017-08-13 18:11:58 +00:00
if ( options . mapFun ) {
const result = options . mapFun ( arr ) ;
return result || arr ;
} else {
return arr ;
}
} ) ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
const result = {
draw : params . draw ,
recordsTotal ,
recordsFiltered ,
data : rowsOfArray
} ;
2017-06-21 00:14:14 +00:00
2017-08-13 18:11:58 +00:00
return result ;
}
2017-06-21 00:14:14 +00:00
}
2017-08-13 18:11:58 +00:00
async function ajaxListWithPermissionsTx ( tx , context , fetchSpecs , params , queryFun , columns , options ) {
2019-01-04 20:31:01 +00:00
enforce ( ! context . user . admin , 'ajaxListWithPermissionsTx is not supposed to be called by assumed admin' ) ;
2017-08-11 06:51:30 +00:00
options = options || { } ;
2017-07-26 19:42:05 +00:00
const permCols = [ ] ;
for ( const fetchSpec of fetchSpecs ) {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( fetchSpec . entityTypeId ) ;
2018-09-27 16:30:23 +00:00
const entityIdColumn = fetchSpec . column ? fetchSpec . column : entityType . entitiesTable + '.id' ;
2017-07-26 19:42:05 +00:00
permCols . push ( {
name : ` permissions_ ${ fetchSpec . entityTypeId } ` ,
query : builder => builder
. from ( entityType . permissionsTable )
. select ( knex . raw ( 'GROUP_CONCAT(operation SEPARATOR \';\')' ) )
2018-09-27 16:30:23 +00:00
. whereRaw ( ` ${ entityType . permissionsTable } .entity = ${ entityIdColumn } ` )
2017-07-26 19:42:05 +00:00
. where ( ` ${ entityType . permissionsTable } .user ` , context . user . id )
. as ( ` permissions_ ${ fetchSpec . entityTypeId } ` )
} ) ;
}
2017-08-13 18:11:58 +00:00
return await ajaxListTx (
tx ,
2017-07-26 19:42:05 +00:00
params ,
builder => {
let query = queryFun ( builder ) ;
for ( const fetchSpec of fetchSpecs ) {
2018-08-03 11:35:55 +00:00
const entityType = entitySettings . getEntityType ( fetchSpec . entityTypeId ) ;
2017-07-26 19:42:05 +00:00
2017-07-27 14:11:22 +00:00
if ( fetchSpec . requiredOperations ) {
2019-01-04 20:31:01 +00:00
const requiredOperations = shares . filterPermissionsByRestrictedAccessHandler ( context , fetchSpec . entityTypeId , null , fetchSpec . requiredOperations , 'ajaxListWithPermissionsTx' ) ;
2018-09-27 16:30:23 +00:00
const entityIdColumn = fetchSpec . column ? fetchSpec . column : entityType . entitiesTable + '.id' ;
2019-01-04 20:31:01 +00:00
if ( requiredOperations . length > 0 ) {
query = query . innerJoin (
function ( ) {
return this . from ( entityType . permissionsTable ) . distinct ( 'entity' ) . where ( 'user' , context . user . id ) . whereIn ( 'operation' , requiredOperations ) . as ( ` permitted__ ${ fetchSpec . entityTypeId } ` ) ;
} ,
` permitted__ ${ fetchSpec . entityTypeId } .entity ` , entityIdColumn )
} else {
query = query . whereRaw ( 'FALSE' ) ;
}
2017-07-27 14:11:22 +00:00
}
2017-07-26 19:42:05 +00:00
}
return query ;
} ,
[
... columns ,
... permCols
] ,
2017-08-11 06:51:30 +00:00
{
mapFun : data => {
for ( let idx = 0 ; idx < fetchSpecs . length ; idx ++ ) {
data [ columns . length + idx ] = data [ columns . length + idx ] . split ( ';' ) ;
}
if ( options . mapFun ) {
const result = options . mapFun ( data ) ;
return result || data ;
} else {
return data ;
}
} ,
2017-08-22 06:15:13 +00:00
orderByBuilder : options . orderByBuilder ,
extraColumns : options . extraColumns
2017-07-26 19:42:05 +00:00
}
) ;
}
2017-08-13 18:11:58 +00:00
async function ajaxList ( params , queryFun , columns , options ) {
return await knex . transaction ( async tx => {
2018-01-27 15:37:14 +00:00
return await ajaxListTx ( tx , params , queryFun , columns , options )
2017-08-13 18:11:58 +00:00
} ) ;
}
async function ajaxListWithPermissions ( context , fetchSpecs , params , queryFun , columns , options ) {
return await knex . transaction ( async tx => {
2018-01-27 15:37:14 +00:00
return await ajaxListWithPermissionsTx ( tx , context , fetchSpecs , params , queryFun , columns , options )
2017-08-13 18:11:58 +00:00
} ) ;
}
2019-01-04 20:31:01 +00:00
module . exports . ajaxListTx = ajaxListTx ;
module . exports . ajaxList = ajaxList ;
module . exports . ajaxListWithPermissionsTx = ajaxListWithPermissionsTx ;
module . exports . ajaxListWithPermissions = ajaxListWithPermissions ;