// Types import { IAttachment, IElasticRangeQueryParams, IRestApiResponse, IRootElasticQuery, } from 'superdesk-api'; import { ASSET_LIST_STYLE, ASSET_SORT_FIELD, ASSET_STATE, ASSET_TYPE_FILTER, IAssetItem, IAssetSearchParams, SORT_ORDER, IUploadAssetModalProps, ISetItem, SET_STATE, } from '../interfaces'; import {superdeskApi} from '../apis'; // Redux Actions & Selectors import {getStore} from '../store'; import {loadSets} from '../store/sets/actions'; import {getSetsById} from '../store/sets/selectors'; // Utils import {fixItemResponseVersionDates, fixItemVersionDates} from './common'; import {getApiErrorMessage, isSamsApiError} from '../utils/api'; // UI import {showModalConnectedToStore} from '../utils/ui'; import {UploadAssetModal} from '../components/assets/uploadAssetModal'; const RESOURCE = 'sams/assets'; const COUNT_RESOURCE = `${RESOURCE}/counts/`; const BINARY_RESOURCE = `${RESOURCE}/binary/`; const COMPRESSED_BINARY_RESOURCE = `${RESOURCE}/compressed_binary/`; const LOCK_ASSET = `${RESOURCE}/lock`; const UNLOCK_ASSET = `${RESOURCE}/unlock`; export function uploadAsset( data: FormData, onProgress: (event: ProgressEvent) => void, ): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.uploadFileWithProgress( '/' + RESOURCE, data, onProgress, ) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to upload file.')); } return Promise.reject(error); }); } const GRID_PAGE_SIZE = 25; const LIST_PAGE_SIZE = 50; function querySearchString(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.textSearch != null && params.textSearch.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.queryString({ query: params.textSearch, lenient: true, default_operator: 'AND', }), ); } } function querySetId(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.setId != null && params.setId.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.term({ field: 'set_id', value: params.setId, }), ); } } function querySetIds(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.setIds != null && params.setIds.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.terms({ field: 'set_id', value: params.setIds, }), ); } } function queryState(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.state != null && params.state.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.term({ field: 'state', value: params.state, }), ); } } function queryStates(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.states != null && params.states.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.terms({ field: 'state', value: params.states, }), ); } } function queryName(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.name != null && params.name.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.queryString({ query: `name:(${params.name})`, lenient: true, default_operator: 'AND', }), ); } } function queryFilename(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.filename != null && params.filename.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.queryString({ query: `filename:(${params.filename})`, lenient: true, default_operator: 'AND', }), ); } } function queryDescription(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.description != null && params.description.length > 0) { source.query.bool.must.push( superdeskApi.elasticsearch.queryString({ query: `description:(${params.description})`, lenient: false, default_operator: 'AND', }), ); } } function queryMimetypes(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.mimetypes === ASSET_TYPE_FILTER.DOCUMENTS) { source.query.bool.must_not.push( superdeskApi.elasticsearch.queryString({ query: 'mimetype:(image\\/*) OR mimetype:(video\\/*) OR mimetype:(audio\\/*)', lenient: true, default_operator: 'OR', }), ); } else if (params.mimetypes !== ASSET_TYPE_FILTER.ALL) { let typeString: string; switch (params.mimetypes) { case ASSET_TYPE_FILTER.IMAGES: typeString = 'image'; break; case ASSET_TYPE_FILTER.VIDEOS: typeString = 'video'; break; case ASSET_TYPE_FILTER.AUDIO: typeString = 'audio'; break; } if (typeString != null) { source.query.bool.must.push( superdeskApi.elasticsearch.queryString({ query: `mimetype:(${typeString}\\/*)`, lenient: true, default_operator: 'OR', }), ); } } } function queryDateRange(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.dateFrom != null || params.dateTo != null) { const args: IElasticRangeQueryParams = {field: '_updated'}; if (params.dateFrom != null) { args.gte = params.dateFrom.toISOString(); } if (params.dateTo != null) { args.lte = params.dateTo.toISOString(); } source.query.bool.must.push( superdeskApi.elasticsearch.range(args), ); } } function querySizeRange(source: IRootElasticQuery, params: IAssetSearchParams) { if (params.sizeFrom != null || params.sizeTo != null) { const args: IElasticRangeQueryParams = {field: 'length'}; if (params.sizeFrom != null) { args.gte = params.sizeFrom * 1048576; // MB -> bytes } if (params.sizeTo != null) { args.lte = params.sizeTo * 1048576; // MB -> bytes } source.query.bool.must.push( superdeskApi.elasticsearch.range(args), ); } } export function queryAssets( params: IAssetSearchParams, listStyle: ASSET_LIST_STYLE, ): Promise> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; const pageSize = listStyle === ASSET_LIST_STYLE.GRID ? GRID_PAGE_SIZE : LIST_PAGE_SIZE; const source: IRootElasticQuery = { query: { bool: { must: [], must_not: [], }, }, size: pageSize, from: (params.page - 1) * pageSize, }; [ querySearchString, querySetId, querySetIds, queryName, queryDescription, queryFilename, queryMimetypes, queryState, queryStates, queryDateRange, querySizeRange, ].forEach((func) => func(source, params)); if (source.query.bool.must.length === 0) { delete source.query.bool.must; } if (source.query.bool.must_not.length === 0) { delete source.query.bool.must_not; } if (Object.keys(source.query.bool).length === 0) { delete source.query; } const sortOrder = params.sortOrder === SORT_ORDER.ASCENDING ? 1 : 0; const sort = `[("${params.sortField}",${sortOrder})]`; return superdeskApi.dataApi.queryRawJson>( RESOURCE, { source: JSON.stringify(source), sort: sort, }, ) .then(fixItemResponseVersionDates) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to query Assets')); } return Promise.reject(error); }); } export function getAssetSearchUrlParams(): Partial { const {urlParams} = superdeskApi.browser.location; const {filterUndefined} = superdeskApi.helpers; return filterUndefined({ textSearch: urlParams.getString('textSearch'), setId: urlParams.getString('setId'), name: urlParams.getString('name'), description: urlParams.getString('description'), state: urlParams.getString('state') as ASSET_STATE, filename: urlParams.getString('filename'), mimetypes: urlParams.getString('mimetypes', ASSET_TYPE_FILTER.ALL) as ASSET_TYPE_FILTER, dateFrom: urlParams.getDate('dateFrom'), dateTo: urlParams.getDate('dateTo'), sizeFrom: urlParams.getNumber('sizeFrom'), sizeTo: urlParams.getNumber('sizeTo'), sortField: urlParams.getString('sortField', ASSET_SORT_FIELD.NAME) as ASSET_SORT_FIELD, sortOrder: urlParams.getString('sortOrder', SORT_ORDER.ASCENDING) as SORT_ORDER, }); } export function setAssetSearchUrlParams(params: Partial) { const {urlParams} = superdeskApi.browser.location; urlParams.setString('textSearch', params.textSearch); urlParams.setString('setId', params.setId); urlParams.setString('name', params.name); urlParams.setString('description', params.description); urlParams.setString('state', params.state); urlParams.setString('filename', params.filename); urlParams.setString('mimetypes', params.mimetypes); urlParams.setDate('dateFrom', params.dateFrom); urlParams.setDate('dateTo', params.dateTo); urlParams.setNumber('sizeFrom', params.sizeFrom); urlParams.setNumber('sizeTo', params.sizeTo); urlParams.setString('sortField', params.sortField); urlParams.setString('sortOrder', params.sortOrder); } export function getAssetsCount(set_ids: Array): Promise> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.queryRawJson>( COUNT_RESOURCE + JSON.stringify(set_ids), ) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to get assets counts for sets')); } return Promise.reject(error); }); } export function getAssetById(assetId: string): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.findOne(RESOURCE, assetId) .then(fixItemVersionDates) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext(`Failed to get asset "${assetId}"`)); } return Promise.reject(error); }); } export function getAssetsByIds(ids: Array): Promise> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.queryRawJson>( RESOURCE, {source: JSON.stringify({ query: { bool: { must: [ superdeskApi.elasticsearch.terms({ field: '_id', value: ids, }), ], }, }, })}, ) .then(fixItemResponseVersionDates) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to get Assets')); } return Promise.reject(error); }); } export function updateAssetMetadata( originalAsset: IAssetItem, originalAttachment: IAttachment, updates: Partial, ): Promise<[IAttachment, IAssetItem]> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return Promise.all([ superdeskApi.entities.attachment.save(originalAttachment, { ...originalAttachment, title: updates.name, description: updates.description, internal: updates.state !== ASSET_STATE.PUBLIC, }), superdeskApi.dataApi.patch(RESOURCE, originalAsset, updates) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to update asset.')); } return Promise.reject(error); }), ]); } export function showUploadAssetModal(props?: Partial): void { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; const store = getStore(); if (store === undefined) { throw new Error('SAMS store has not been initialised'); } // (re)load all the Sets into the Redux store store.dispatch(loadSets()) .then((sets: Array) => { // Check if there are any usable Sets found // Otherwise notify the user to enable one first if (sets.filter((set) => set.state === SET_STATE.USABLE).length === 0) { notify.error(gettext('No usable Sets found. Enable one first')); return; } // Finally show the upload asset modal showModalConnectedToStore>( UploadAssetModal, { dark: true, modalSize: 'fill', ...props ?? {}, }, ); }); } export function getAssetBinary(asset: IAssetItem): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; const {downloadBlob} = superdeskApi.utilities; return superdeskApi.dataApi.queryRaw( BINARY_RESOURCE + asset._id, ) .then((res) => res.arrayBuffer() .then((blob: any) => { downloadBlob(blob, res.headers.get('Content-type')!, asset.filename); })) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to get binary for asset')); } return Promise.reject(error); }); } export function getAssetsCompressedBinary(asset_ids: Array): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; const {downloadBlob} = superdeskApi.utilities; return superdeskApi.dataApi.queryRaw( COMPRESSED_BINARY_RESOURCE + JSON.stringify(asset_ids), ) .then((res) => res.arrayBuffer() .then((blob: any) => { downloadBlob(blob, 'application/zip', 'download'); })) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to get compressed binaries for assets')); } return Promise.reject(error); }); } export function deleteAsset(item: IAssetItem): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.delete(RESOURCE, item) .then(() => { notify.success(gettext('Asset deleted successfully')); return Promise.resolve(); }) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else if (error._message != null) { notify.error(gettext('Error: {{message}}', {message: error._message})); } else { notify.error(gettext('Failed to delete the Asset')); } return Promise.reject(error); }); } export function updateAsset(original: IAssetItem, updates: Partial): Promise { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.patch(RESOURCE, original, updates) .then((asset: IAssetItem) => { notify.success(gettext('Asset updated successfully')); return asset; }) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to update the Asset')); } return Promise.reject(error); }); } export function lockAsset(original: IAssetItem, updates: Dictionary): Promise> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.patch(LOCK_ASSET, original, updates) .then((asset: IAssetItem) => { return asset; }) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to lock the Asset')); } return Promise.reject(error); }); } export function unlockAsset(original: IAssetItem, updates: Dictionary): Promise> { const {gettext} = superdeskApi.localization; const {notify} = superdeskApi.ui; return superdeskApi.dataApi.patch(UNLOCK_ASSET, original, updates) .then((asset: IAssetItem) => { return asset; }) .catch((error: any) => { if (isSamsApiError(error)) { notify.error(getApiErrorMessage(error)); } else { notify.error(gettext('Failed to unlock the Asset')); } return Promise.reject(error); }); } export function getSetsSync(): Dictionary { const store = getStore(); if (store === undefined) { throw new Error('SAMS store has not been initialised'); } return getSetsById(store.getState()); }