@universis/docutracks
Version:
Implementation of document numbering services hosted by docutracks
341 lines (331 loc) • 15.2 kB
JavaScript
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