public/lib/content-service.js (233 lines of code) (raw):
import angular from 'angular';
import './composer-service';
import './media-atom-maker-service'
import './atom-workshop-service'
import './http-session-service';
import './user';
import './visibility-service';
import { provideFormats } from './model/format-helpers.ts'
angular.module('wfContentService', ['wfHttpSessionService', 'wfVisibilityService', 'wfDateService', 'wfFiltersService', 'wfUser', 'wfComposerService', 'wfMediaAtomMakerService', 'wfAtomWorkshopService', 'wfPreferencesService'])
.factory('wfContentService', ['$rootScope', '$log', 'wfHttpSessionService', 'wfDateParser', 'wfFormatDateTimeFilter', 'wfFiltersService', 'wfComposerService', 'wfMediaAtomMakerService', 'wfAtomWorkshopService', 'wfPreferencesService', 'config',
function ($rootScope, $log, wfHttpSessionService, wfDateParser, wfFormatDateTimeFilter, wfFiltersService, wfComposerService, wfMediaAtomMakerService, wfAtomWorkshopService, wfPreferencesService, config) {
const httpRequest = wfHttpSessionService.request;
class ContentService {
getTypes() {
return wfPreferencesService.getPreference('featureSwitches')
.then((featureSwitches) => {
return provideFormats(featureSwitches)
})
.catch((err) => { return provideFormats() })
}
/* what types of stub should be treated as atoms? */
getAtomTypes() {
return config.atomTypes.reduce((allowedTypes, type) => {
allowedTypes[type] = true;
return allowedTypes;
}, {});
}
getEditorUrl(editorId, atomType) {
if (atomType === "media") {
return config.mediaAtomMakerViewAtom + editorId;
} else if (atomType === "chart") {
return `${config.atomWorkshopViewAtom}/${atomType.toUpperCase()}/${editorId}/edit`;
}
};
/**
* Async retrieves content from service.
*
* @param {Object} params
* @returns {Promise}
*/
get(params) {
return httpRequest({
method: 'GET',
url: '/api/content',
params: params
});
}
/**
* Async creates a stub in workflow.
*/
createStub(stubData) {
var params = {};
return httpRequest({
method: 'POST',
url: '/api/stubs',
params: params,
data: stubData
});
}
getById(composerId) {
return httpRequest({
method: 'GET',
url: '/api/content/' + composerId
});
}
getByEditorId(editorId) {
return httpRequest({
method: 'GET',
url: '/api/atom/' + editorId
});
}
/**
* Creates a draft in Composer from a Stub. Effectively moving
* it to "Writers" status.
* Also will create the stub if it doesn't have an id.
*/
createInComposer(stub, statusOption) {
return wfComposerService.create(stub.contentType, stub.commissioningDesks, stub.commissionedLength, stub.prodOffice, stub.template, stub.articleFormat, stub.priority, stub.missingCommissionedLengthReason)
.then((response) => wfComposerService.parseComposerData(response, stub))
.then((updatedStub) => {
// log uses of the missingCommissionedLengthReason option
if (stub.missingCommissionedLengthReason) {
const data = {
message: `Draft created without commissionedLength: ${stub.missingCommissionedLengthReason}`,
missingCommissionedLengthReason: stub.missingCommissionedLengthReason,
composerId: updatedStub.composerId ?? null,
prodOffice: updatedStub.prodOffice,
contentType: updatedStub.contentType,
section: updatedStub.section?.name,
title: updatedStub.title,
}
$log.info(JSON.stringify(data))
}
if (statusOption) {
updatedStub['status'] = statusOption;
}
if (stub.id) {
return this.updateStub(updatedStub);
} else {
return this.createStub(updatedStub);
}
});
}
/**
* Creates an atom. Effectively setting
* the editorId to what we get from the response.
*/
createInAtomEditor(stub, statusOption) {
const that = this;
function processAtomEditorCreateResponse(response) {
stub['editorId'] = response.data.id;
if (statusOption) {
stub['status'] = statusOption;
}
if (stub.id) {
return that.updateStub(stub);
} else {
return that.createStub(stub);
}
}
var createResponse = stub.contentType === 'media' ? wfMediaAtomMakerService.create(stub.title) : wfAtomWorkshopService.create(stub.contentType, stub.title);
return createResponse.then(processAtomEditorCreateResponse);
}
/**
* Updates an existing stub by overwriting its fields via PUT.
*/
updateStub(stub) {
return httpRequest({
method: 'PUT',
url: '/api/stubs/' + stub.id,
data: { 'stub': stub, 'collaborators': null }
});
}
/**
* Async update a field for a piece of content.
*
* Adapter for both content and stubs APIs.
* TODO normalise to single API.
*
* @param {Object} contentItem
* @param {String} field
* @param {mixed} data
* @param {String} contentType
*
* @returns {Promise}
*/
updateField(contentItem, field, data, contentType) {
if (field === 'status' && contentItem.status === 'Stub') {
if (wfAtomService.atomTypes.indexOf(contentType) >= 0) {
return this.createInAtomEditor(contentItem, data);
} else {
return this.createInComposer(contentItem, data)
}
}
var contentId = contentItem.id || contentItem.stubId;
// TODO: create a generic PATCH / PUT API
return httpRequest({
method: 'PUT',
url: '/api/stubs/' + contentId + '/' + field,
data: { 'data': data }
});
}
/**
* Link existing stub to composer article.
*/
updateComposerId(stubId, composerId, contentType) {
return httpRequest({
method: 'POST', // TODO: update to PATCH method
url: '/api/stubs/' + stubId,
params: { 'composerId': composerId, 'contentType': contentType }
});
}
/**
* Async deletes content.
*
* @param {String} stubId ID of stub to delete.
* @returns {Promise}
*/
remove(stubId) {
return httpRequest({
method: 'DELETE',
url: '/api/stubs/' + stubId
});
}
/**
* Formats model params into params to send to server.
*
* @param {Object} params
* @returns {Object}
*/
getServerParams() {
var modelParams = wfFiltersService.getAll();
var deadline = modelParams['deadline'];
var dateRange = wfDateParser.parseRangeFromString(deadline);
var createdRange = wfDateParser.parseRangeFromString(modelParams['created']);
var viewRange = wfDateParser.parseRangeFromString(modelParams['view']);
var params = {
'status': modelParams['status'],
'state': modelParams['state'],
'section': modelParams['section'],
'content-type': modelParams["content-type"],
'atom-type': modelParams["atom-type"],
'flags': modelParams['flags'],
'prodOffice': modelParams['prodOffice'],
'due.from': wfFormatDateTimeFilter(wfDateParser.getFromDate(dateRange['from']), "ISO8601") || null,
'due.until': wfFormatDateTimeFilter(dateRange['until'], "ISO8601") || null,
'created.from': wfFormatDateTimeFilter(wfDateParser.getFromDate(createdRange['from']), "ISO8601") || null,
'created.until': wfFormatDateTimeFilter(createdRange['until'], "ISO8601") || null,
'view.from': wfFormatDateTimeFilter(wfDateParser.getFromDate(viewRange['from']), "ISO8601") || null,
'view.until': wfFormatDateTimeFilter(viewRange['until'], "ISO8601") || null,
'text': modelParams['text'] || null,
'assignee': modelParams['assignee'] || null,
'touched': modelParams['touched'] || null,
'assigneeEmail': modelParams['assigneeEmail'] || null,
'incopy': modelParams['incopy'] || null,
'composerId': modelParams['composerId'] || null,
'editorId': modelParams['editorId'] || null,
'trashed': modelParams['trashed'] || null,
'hasPrintInfo': modelParams['hasPrintInfo'] || null,
'hasMainMedia': modelParams['hasMainMedia'] || null,
'rights': modelParams['rights'] || null,
'display-hint': modelParams["display-hint"],
'article-format': modelParams["article-format"],
};
return params;
}
}
return new ContentService();
}])
/**
* Content polling service.
*
* TODO: replace with an event stream
*/
.factory('wfContentPollingService', ['$http', '$timeout', '$rootScope', 'wfContentService', function ($http, $timeout, $rootScope, wfContentService) {
var POLLING_DELAY = 5000;
class ContentPollingService {
/**
* @param {function} paramsProvider used to retrieve filter params for the scope
* at the instant the next poll occurs. Necessary to
* cater for changes in filters.
*/
constructor(paramsProvider) {
this._paramsProvider = paramsProvider;
this.currentSearch = undefined
this.init();
}
init() {
// event provided by visibility service
$rootScope.$on('visibility.changed', (function (event, data) {
if (data.visibility) {
this.startPolling();
} else {
this.stopPolling();
}
}).bind(this));
}
// single callback only required so far...
onPoll(callback) {
this._callback = callback;
}
onError(callback) {
this._errorCallback = callback;
}
startPolling() {
return this.refresh();
}
stopPolling() {
if (this._timer) {
$timeout.cancel(this._timer);
this._timer = false;
}
}
/**
* Forces a poll to the server to refresh data. Resets the poll
* timer for the next subsequent poll.
*/
refresh() {
this.stopPolling();
const localSearch = this._paramsProvider()
this.currentSearch = localSearch
return wfContentService.get(localSearch)
.then((cb) => {
const localSearchIsStale = localSearch !== this.currentSearch
if (localSearchIsStale) {
// This means that, since getting results,
// the search terms have changed, so we can ignore the response
return
}
return this._callback(cb)
})
.then(() => {
this._timer = $timeout(this.refresh.bind(this), POLLING_DELAY);
})
.catch((err) => {
if (this._errorCallback) {
this._errorCallback(err);
}
});
}
}
return ContentPollingService;
}]);