UNPKG

@universis/docutracks

Version:

Implementation of document numbering services hosted by docutracks

341 lines (331 loc) 15.2 kB
import { ApplicationService, HttpNotAcceptableError, Args, DataError, TraceUtils, HttpError } from '@themost/common'; import { DocumentNumberService, DefaultDocumentNumberService } from '@universis/docnumbers'; import fetch, { Headers } from 'node-fetch'; import { EdmMapping, EdmType, DataPermissionEventListener, PermissionMask, SchemaLoaderStrategy, ModelClassLoaderStrategy, ODataModelBuilder } from '@themost/data'; import { promisify } from 'util'; const DocumentReferenceKind = { Default: { Id: 1 }, }; const DocumentReferenceType = { Incoming: { Id: 1 }, Outgoing: { Id: 2 }, }; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function __awaiter(thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); } let DepartmentDocumentNumberSeries = class DepartmentDocumentNumberSeries { static getGroups(context) { return __awaiter(this, void 0, void 0, function* () { // validate permissions const validator = new DataPermissionEventListener(); // noinspection JSUnresolvedFunction const validateAsync = promisify(validator.validate); // get first user department const user = yield context.model('User').where('name').equal(context.user.name).getTypedItem(); const userDepartments = user && user.property('departments'); let firstUserDepartment = null; if (userDepartments) { firstUserDepartment = yield userDepartments.select('id').getItem(); } // validate create permissions yield validateAsync({ model: context.model('DepartmentDocumentNumberSeries'), mask: PermissionMask.Create, target: { department: firstUserDepartment }, }); const service = context.getApplication().getService(DocumentNumberService); if (typeof service.getGroups === 'function') { return yield service.getGroups(); } }); } }; __decorate([ EdmMapping.func('Groups', EdmType.CollectionOf('Object')) ], DepartmentDocumentNumberSeries, "getGroups", null); DepartmentDocumentNumberSeries = __decorate([ EdmMapping.entityType('DocumentNumberSeries') ], DepartmentDocumentNumberSeries); class DepartmentDocumentNumberSeriesReplacer extends ApplicationService { constructor(app) { super(app); } apply() { // get schema loader const schemaLoader = this.getApplication().getConfiguration().getStrategy(SchemaLoaderStrategy); // get model definition const model = schemaLoader.getModelDefinition('DepartmentDocumentNumberSeries'); // get model class const loader = this.getApplication().getConfiguration().getStrategy(ModelClassLoaderStrategy); const DepartmentDocumentNumberSeriesBase = loader.resolve(model); // extend class DepartmentDocumentNumberSeriesBase.getGroups = DepartmentDocumentNumberSeries.getGroups; const builder = this.getApplication().getStrategy(ODataModelBuilder); if (builder != null) { builder.addEntity('OrganizationGroupItem') .addProperty('id', EdmType.EdmInt32, false) .addProperty('name', EdmType.EdmString, false) .addProperty('alternateName', EdmType.EdmString, false) .addProperty('path', EdmType.EdmString, false); builder.getEntity('DepartmentDocumentNumberSeries').collection .addFunction('Groups').returnsCollection('OrganizationGroupItem'); } } } class LoginError extends Error { constructor() { super('Failed to authenticate process against current document number service'); } } class InvalidIdentifierError extends Error { constructor() { super('The specified document series has an invalid identifier'); } } function assignHeaders(source, addHeaders) { if (addHeaders) { addHeaders.forEach((value, name) => { source.append(name, value); }); } return source; } class DocutracksNumberService extends DefaultDocumentNumberService { constructor(app) { super(app); this.options = app.getConfiguration().getSourceAt('settings/universis/docutracks'); // apply changes to document series model new DepartmentDocumentNumberSeriesReplacer(app).apply(); } /** * Validates the content type of the given response * @param {Response} response */ validateContentType(response) { // get content type const contentType = response.headers.get('Content-Type'); if (/^application\/json;/g.test(contentType) === false) { throw new HttpNotAcceptableError(`Document service response is invalid. Expected a valid json but got ${contentType}`); } } login() { return __awaiter(this, void 0, void 0, function* () { const loginResponse = yield fetch(new URL('/services/authentication/login', this.options.server).toString(), { method: 'POST', body: JSON.stringify({ UserName: this.options.user, Password: this.options.password }), headers: new Headers({ 'Content-Type': 'application/json' }) }); if (loginResponse.ok) { // validate response this.validateContentType(loginResponse); // get body const body = yield loginResponse.json(); if (body == null) { throw new Error('The response returned by document service is invalid.'); } if (body && body.Success === false) { throw new LoginError(); } // return cookie const setCookie = loginResponse.headers.get('Set-Cookie'); return setCookie.split(',').map((v) => v.trim()).find((v) => { return /^\.ASPXAUTH=/.test(v); }); } else { throw new LoginError(); } }); } getGroupFrom(alternateName) { const matches = /^\/Groups\/(\d+)$/ig.exec(alternateName); if (matches == null) { throw new InvalidIdentifierError(); } return parseInt(matches[1], 10); } next(context, documentSeries, extraAttributes) { return __awaiter(this, void 0, void 0, function* () { Args.check(documentSeries != null, 'Document series cannot be empty at this context.'); // get document series alternate name const alternateName = yield context.model('DocumentNumberSeries').where('id').equal(documentSeries.id) .select('alternateName').silent().value(); if (alternateName == null) { throw new DataError('E_ATTRIBUTE', 'The specified document series does not have an alternate name or it\'s not accessible.', null, 'DocumentNumberSeries', 'alternateName'); } // try to login const authenticationCookie = yield this.login(); // the alternate name should have a format like /Groups/104 // get group identifier from the given alternate name const value = this.getGroupFrom(alternateName); // set group reference const group = { Id: value }; const title = extraAttributes && extraAttributes.description; // create request payload const documentRequest = { Document: { Title: title, Attachments: [], Kind: DocumentReferenceKind.Default, Type: DocumentReferenceType.Outgoing, CreatedByGroup: group, CreatedForGroup: group, DocumentCopies: [ { CreatedByGroup: group, OwnedByGroup: group } ] } }; const userContext = context; if (userContext.user && userContext.user.name) { // try to get user const findUser = yield this.getUser(userContext.user.name); if (findUser.User != null) { documentRequest.Document.CreatedBy = findUser.User; } } const headers1 = assignHeaders(new Headers({ 'Content-Type': 'application/json', 'Cookie': authenticationCookie }), this.headers); // register document const response1 = yield fetch(new URL('/services/document/register', this.options.server).toString(), { method: 'POST', body: JSON.stringify(documentRequest), headers: headers1 }); // if response is ok if (response1.ok) { this.validateContentType(response1); // get response const documentResponse = yield response1.json(); // if the operation has been failed if (documentResponse.Success === false) { TraceUtils.error('DocutracksNumberService', documentRequest, documentResponse); // throw error throw new DataError('E_DOC_NUMBER_ERROR', documentResponse.ErrorInfo.ExceptionMessage || documentResponse.ErrorInfo.ErrorMessage, null); } // otherwise return document number return documentResponse.DocumentInfo.ProtocolText; } }); } getUser(name) { return __awaiter(this, void 0, void 0, function* () { // try to login const authenticationCookie = yield this.login(); const headers = assignHeaders(new Headers({ 'Content-Type': 'application/json', 'Cookie': authenticationCookie }), this.headers); const response = yield fetch(new URL('/services/user/get/byusername', this.options.server).toString(), { method: 'POST', body: JSON.stringify({ Username: name }), // tslint:disable-next-line: object-literal-shorthand headers: headers }); if (response.ok) { this.validateContentType(response); const res = yield response.json(); return res; } throw new HttpError(response.status, 'An error occurred while getting user', response.statusText); }); } getGroups() { return __awaiter(this, void 0, void 0, function* () { // try to login const authenticationCookie = yield this.login(); // register document const headers = assignHeaders(new Headers({ 'Content-Type': 'application/json', 'Cookie': authenticationCookie }), this.headers); const response = yield fetch(new URL('/services/organization/fullUsersTree', this.options.server).toString(), { method: 'GET', // tslint:disable-next-line: object-literal-shorthand headers: headers }); if (response.ok) { this.validateContentType(response); const body = yield response.json(); const results = []; if (Array.isArray(body)) { function extractGroups(group, parentPath) { const res = []; const addGroup = { id: group.Id, name: group.DisplayName, alternateName: `/Groups/${group.Id}`, path: `${parentPath}/${group.DisplayName}` }; res.push(addGroup); if (group.SubGroups) { group.SubGroups.forEach((item) => { const addGroups = extractGroups(item, addGroup.path); res.push.apply(res, addGroups); }); } return res; } body.forEach((item) => { results.push.apply(results, extractGroups(item, '')); }); } return results; } throw new Error('An error occurred while getting organization groups'); }); } add(context, file, item) { return super.add(context, file, item); } replace(context, file, item) { return super.replace(context, file, item); } } export { DocumentReferenceKind, DocumentReferenceType, DocutracksNumberService, InvalidIdentifierError, LoginError }; //# sourceMappingURL=index.esm.js.map