import { Knex } from 'knex' ; import getDatabase from '../database' ; import { systemCollectionRows } from '../database/system-data/collections' ; import { ForbiddenException , InvalidPayloadException } from '../exceptions' ; import { AbstractServiceOptions , PrimaryKey , SchemaOverview } from '../types' ; import { Accountability } from '@directus/shared/types' ; export class UtilsService { knex : Knex ; accountability : Accountability | null ; schema : SchemaOverview ; constructor ( options ) { this . knex = options . knex || getDatabase ( ) ; this . accountability = options . accountability || null ; this . schema = options . schema ; } async sort ( collection , { item , to } ) { const sortFieldResponse = ( await this . knex . select ( 'sort_field' ) . from ( 'directus_collections' ) . where ( { collection } ) . first ( ) ) || systemCollectionRows ; const sortField = sortFieldResponse ?. sort_field ; if ( ! sortField ) { throw new InvalidPayloadException ( `Collection "${ collection }" doesn't have a sort field.` ) ; } if ( this . accountability ?. admin !== true ) { const permissions = this . accountability ?. permissions ?. find ( ( permission ) => { return permission . collection === collection && permission . action === 'update' ; } ) ; if ( ! permissions ) { throw new ForbiddenException ( ) ; } const allowedFields = permissions . fields ?? [ ] ; if ( allowedFields [ 0 ] !== '*' && allowedFields . includes ( sortField ) === false ) { throw new ForbiddenException ( ) ; } } const primaryKeyField = this . schema . collections [ collection ] . primary ; const countResponse = await this . knex . count ( '* as count' ) . from ( collection ) . whereNull ( sortField ) . first ( ) ; if ( countResponse ?. count && + countResponse . count !== 0 ) { const lastSortValueResponse = await this . knex . max ( sortField ) . from ( collection ) . first ( ) ; const rowsWithoutSortValue = await this . knex . select ( primaryKeyField , sortField ) . from ( collection ) . whereNull ( sortField ) ; let lastSortValue = lastSortValueResponse ? Object . values ( lastSortValueResponse ) [ 0 ] : 0 ; for ( const row of rowsWithoutSortValue ) { lastSortValue ++ ; await this . knex ( collection ) . update ( { [ sortField ] : lastSortValue } ) . where ( { [ primaryKeyField ] : row [ primaryKeyField ] } ) ; } } const duplicates = await this . knex . select ( sortField ) . count ( sortField , { as : 'count' } ) . groupBy ( sortField ) . from ( collection ) . havingRaw ( 'count(??) > 1' , [ sortField ] ) ; if ( duplicates ?. length > 0 ) { const ids = await this . knex . select ( primaryKeyField ) . from ( collection ) . orderBy ( sortField ) ; for ( let i = 0 ; i < ids . length ; i ++ ) { await this . knex ( collection ) . update ( { [ sortField ] : i + 1 } ) . where ( ids [ i ] ) ; } } const targetSortValueResponse = await this . knex . select ( sortField ) . from ( collection ) . where ( { [ primaryKeyField ] : to } ) . first ( ) ; const targetSortValue = targetSortValueResponse [ sortField ] ; const sourceSortValueResponse = await this . knex . select ( sortField ) . from ( collection ) . where ( { [ primaryKeyField ] : item } ) . first ( ) ; const sourceSortValue = sourceSortValueResponse [ sortField ] ; await this . knex ( collection ) . update ( { [ sortField ] : targetSortValue } ) . where ( { [ primaryKeyField ] : item } ) ; if ( sourceSortValue < targetSortValue ) { await this . knex ( collection ) . decrement ( sortField , 1 ) . where ( sortField , '>' , sourceSortValue ) . andWhere ( sortField , '<=' , targetSortValue ) . andWhereNot ( { [ primaryKeyField ] : item } ) ; } else { await this . knex ( collection ) . increment ( sortField , 1 ) . where ( sortField , '>=' , targetSortValue ) . andWhere ( sortField , '<=' , sourceSortValue ) . andWhereNot ( { [ primaryKeyField ] : item } ) ; } } }