TokenDao: class=>interface
TokenDao has been renamed to MongoTokenDao. While it's good for this to have its own interface anyway, the immediate motivation was for unit tests: I started playing with mocking with TypeScript and researching some libraries, but I don't have time to research enough to commit to any of them at the moment. Interfaces remove the need to mock at all. This also stops using `export default' in favor of just importing by name. Using defaults only saves us a few characters, and it makes for awkward syntax in various cases (e.g. with multiple exports). But I'm still new to TS, so who knows if I'll be flip-flopping on this decision in the future. If we kept to our normal 1:1 file:definition convention, it wouldn't cause problems, but based on the types I've had to define so far, that'd cause way too much bloat and boilerplate. * src/server/daemon/controller.js: No long import `default'. Use `MongoTokenDao'. * src/server/token/TokenedService.js: Stop checking type (since TS interfaces do not result in compiler output, easejs cannot validate against them.) * src/server/token/MongoTokenDao.ts: Rename from `TokenDao.ts'. * src/server/token/TokenDao.ts: Rename from `TokenQueryResult.ts'. (TokenDao): New interface. * src/server/token/TokenQueryResult.ts: Rename to `TokenDao.ts'. * test/server/token/MongoTokenDaoTest.ts: Rename from `TokenDaoTest.ts'.master
parent
37f1b86ac1
commit
fb88ceeae6
|
@ -90,8 +90,8 @@ const {
|
||||||
},
|
},
|
||||||
|
|
||||||
token: {
|
token: {
|
||||||
TokenDao: {
|
MongoTokenDao: {
|
||||||
default: TokenDao,
|
MongoTokenDao
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ function _initExportService( db, callback )
|
||||||
ExportService
|
ExportService
|
||||||
.use( TokenedService(
|
.use( TokenedService(
|
||||||
'c1import',
|
'c1import',
|
||||||
new TokenDao( collection, "exports", getUnixTimestamp ),
|
new MongoTokenDao( collection, "exports", getUnixTimestamp ),
|
||||||
function tokgen()
|
function tokgen()
|
||||||
{
|
{
|
||||||
var shasum = crypto.createHash( 'sha1' );
|
var shasum = crypto.createHash( 'sha1' );
|
||||||
|
|
|
@ -21,8 +21,7 @@
|
||||||
|
|
||||||
var Trait = require( 'easejs' ).Trait,
|
var Trait = require( 'easejs' ).Trait,
|
||||||
Class = require( 'easejs' ).Class,
|
Class = require( 'easejs' ).Class,
|
||||||
Service = require( './Service' ),
|
Service = require( './Service' );
|
||||||
TokenDao = require( '../token/TokenDao' ).default;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -96,11 +95,6 @@ module.exports = Trait( 'TokenedService' )
|
||||||
*/
|
*/
|
||||||
__mixin: function( namespace, dao, tokgen, capture_gen )
|
__mixin: function( namespace, dao, tokgen, capture_gen )
|
||||||
{
|
{
|
||||||
if ( !Class.isA( TokenDao, dao ) )
|
|
||||||
{
|
|
||||||
throw TypeError( 'Instance of TokenDao expected' );
|
|
||||||
}
|
|
||||||
|
|
||||||
if ( typeof tokgen !== 'function' )
|
if ( typeof tokgen !== 'function' )
|
||||||
{
|
{
|
||||||
throw TypeError( 'Token generator must be a function' );
|
throw TypeError( 'Token generator must be a function' );
|
||||||
|
|
|
@ -0,0 +1,269 @@
|
||||||
|
/**
|
||||||
|
* Token state management
|
||||||
|
*
|
||||||
|
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
||||||
|
*
|
||||||
|
* This file is part of the Liza Data Collection Framework.
|
||||||
|
*
|
||||||
|
* liza is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
* published by the Free Software Foundation, either version 3 of the
|
||||||
|
* License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
TokenDao,
|
||||||
|
TokenData,
|
||||||
|
TokenEntry,
|
||||||
|
TokenNamespaceData,
|
||||||
|
TokenNamespaceResults,
|
||||||
|
TokenQueryResult,
|
||||||
|
TokenStatus,
|
||||||
|
TokenType,
|
||||||
|
} from "./TokenDao";
|
||||||
|
|
||||||
|
import { TokenId, TokenNamespace } from "./Token";
|
||||||
|
import { DocumentId } from "../../document/Document";
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages token updates
|
||||||
|
*
|
||||||
|
* This uses MongoDB as the underlying database.
|
||||||
|
*/
|
||||||
|
export class MongoTokenDao implements TokenDao
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Mongo database collection
|
||||||
|
*/
|
||||||
|
private readonly _collection: MongoCollection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Field storing token data, relative to document root
|
||||||
|
*/
|
||||||
|
private readonly _rootField: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a Unix timestamp
|
||||||
|
*
|
||||||
|
* This is used for timestampping token updates.
|
||||||
|
*/
|
||||||
|
private readonly _getTimestamp: () => UnixTimestamp;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize connection
|
||||||
|
*
|
||||||
|
* @param collection Mongo collection
|
||||||
|
* @param root_field topmost field in mongo document
|
||||||
|
* @param date_ctor Date constructor
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
collection: MongoCollection,
|
||||||
|
root_field: string,
|
||||||
|
getTimestamp: () => UnixTimestamp,
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this._collection = collection;
|
||||||
|
this._rootField = root_field;
|
||||||
|
this._getTimestamp = getTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create or update a token record
|
||||||
|
*
|
||||||
|
* The token entry is entered in the token log, and then the current
|
||||||
|
* entry is updated to reflect the changes. The operation is atomic.
|
||||||
|
*
|
||||||
|
* @param doc_id unique document identifier
|
||||||
|
* @param ns token namespace
|
||||||
|
* @param token token value
|
||||||
|
* @param data token data, if any
|
||||||
|
* @param status arbitrary token type
|
||||||
|
*/
|
||||||
|
updateToken(
|
||||||
|
doc_id: DocumentId,
|
||||||
|
ns: TokenNamespace,
|
||||||
|
token_id: TokenId,
|
||||||
|
type: TokenType,
|
||||||
|
data: string | null,
|
||||||
|
): Promise<void>
|
||||||
|
{
|
||||||
|
const root = this._genRoot( ns ) + '.';
|
||||||
|
|
||||||
|
const token_entry: TokenStatus = {
|
||||||
|
type: type,
|
||||||
|
timestamp: this._getTimestamp(),
|
||||||
|
data: data,
|
||||||
|
};
|
||||||
|
|
||||||
|
const token_data = {
|
||||||
|
[ root + 'last' ]: token_id,
|
||||||
|
[ root + 'lastStatus' ]: token_entry,
|
||||||
|
[ root + token_id + '.status' ]: token_entry,
|
||||||
|
};
|
||||||
|
|
||||||
|
const token_log = {
|
||||||
|
[ root + token_id + '.statusLog' ]: token_entry,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise( ( resolve, reject ) =>
|
||||||
|
{
|
||||||
|
this._collection.update(
|
||||||
|
{ id: +doc_id },
|
||||||
|
{
|
||||||
|
$set: token_data,
|
||||||
|
$push: token_log
|
||||||
|
},
|
||||||
|
{ upsert: true },
|
||||||
|
|
||||||
|
function ( err: Error|null )
|
||||||
|
{
|
||||||
|
if ( err )
|
||||||
|
{
|
||||||
|
reject( err );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve existing token under the namespace NS, if any, for the doc
|
||||||
|
* identified by DOC_ID
|
||||||
|
*
|
||||||
|
* If a TOKEN_ID is provided, only that token will be queried; otherwise,
|
||||||
|
* the most recently created token will be the subject of the query.
|
||||||
|
*
|
||||||
|
* @param doc_id document identifier
|
||||||
|
* @param ns token namespace
|
||||||
|
* @param token_id token identifier (unique to NS)
|
||||||
|
*
|
||||||
|
* @return token data
|
||||||
|
*/
|
||||||
|
getToken( doc_id: DocumentId, ns: TokenNamespace, token_id: TokenId ):
|
||||||
|
Promise<TokenData|null>
|
||||||
|
{
|
||||||
|
const root = this._genRoot( ns ) + '.';
|
||||||
|
const fields: any = {};
|
||||||
|
|
||||||
|
fields[ root + 'last' ] = 1;
|
||||||
|
fields[ root + 'lastStatus' ] = 1;
|
||||||
|
|
||||||
|
if ( token_id )
|
||||||
|
{
|
||||||
|
// XXX: injectable
|
||||||
|
fields[ root + token_id ] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise( ( resolve, reject ) =>
|
||||||
|
{
|
||||||
|
this._collection.findOne(
|
||||||
|
{ id: +doc_id },
|
||||||
|
{ fields: fields },
|
||||||
|
( err: Error|null, data: TokenQueryResult ) =>
|
||||||
|
{
|
||||||
|
if ( err || !data )
|
||||||
|
{
|
||||||
|
reject( err );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = <TokenNamespaceResults>data[ this._rootField ]
|
||||||
|
|| {};
|
||||||
|
|
||||||
|
if ( !field[ ns ] )
|
||||||
|
{
|
||||||
|
resolve( null );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ns_data = <TokenNamespaceData>field[ ns ];
|
||||||
|
|
||||||
|
resolve( ( token_id )
|
||||||
|
? this._getRequestedToken( token_id, ns_data )
|
||||||
|
: this._getLatestToken( ns_data )
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve latest token data, or `null` if none
|
||||||
|
*
|
||||||
|
* @param ns_data namespace data
|
||||||
|
*
|
||||||
|
* @return data of latest token in namespace
|
||||||
|
*/
|
||||||
|
private _getLatestToken( ns_data: TokenNamespaceData ): TokenData | null
|
||||||
|
{
|
||||||
|
var last = ns_data.last;
|
||||||
|
|
||||||
|
if ( !last )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: last,
|
||||||
|
status: ns_data.lastStatus,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve latest token data, or `null` if none
|
||||||
|
*
|
||||||
|
* @param token_id token identifier for namespace associated with NS_DATA
|
||||||
|
* @param ns_data namespace data
|
||||||
|
*
|
||||||
|
* @return data of requested token
|
||||||
|
*/
|
||||||
|
private _getRequestedToken(
|
||||||
|
token_id: TokenId,
|
||||||
|
ns_data: TokenNamespaceData
|
||||||
|
): TokenData | null
|
||||||
|
{
|
||||||
|
const reqtok = <TokenEntry>ns_data[ <string>token_id ];
|
||||||
|
|
||||||
|
if ( !reqtok )
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: token_id,
|
||||||
|
status: reqtok.status,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine token root for the given namespace
|
||||||
|
*
|
||||||
|
* @param ns token namespace
|
||||||
|
*
|
||||||
|
* @return token root for namespace NS
|
||||||
|
*/
|
||||||
|
private _genRoot( ns: TokenNamespace ): string
|
||||||
|
{
|
||||||
|
// XXX: injectable
|
||||||
|
return this._rootField + '.' + ns;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/**
|
/**
|
||||||
* Token state management
|
* Token data access
|
||||||
*
|
*
|
||||||
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
||||||
*
|
*
|
||||||
|
@ -17,21 +17,148 @@
|
||||||
*
|
*
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* These types are used to describe the structure of the token data as it
|
||||||
|
* is stored in Mongo. It has a number of undesirable properties and
|
||||||
|
* duplicates data---this was intended to make querying easier and work
|
||||||
|
* around Mongo limitations.
|
||||||
|
*
|
||||||
|
* This structure can be changed in the future, but we'll need to maintain
|
||||||
|
* compatibility with the existing data.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
|
||||||
TokenEntry,
|
|
||||||
TokenNamespaceData,
|
|
||||||
TokenNamespaceResults,
|
|
||||||
TokenQueryResult,
|
|
||||||
TokenStatus,
|
|
||||||
TokenType,
|
|
||||||
} from "./TokenQueryResult";
|
|
||||||
|
|
||||||
import { TokenId, TokenNamespace } from "./Token";
|
import { TokenId, TokenNamespace } from "./Token";
|
||||||
import { DocumentId } from "../../document/Document";
|
import { DocumentId } from "../../document/Document";
|
||||||
|
|
||||||
|
|
||||||
|
/** Manage token updates */
|
||||||
|
export interface TokenDao
|
||||||
|
{
|
||||||
|
updateToken(
|
||||||
|
doc_id: DocumentId,
|
||||||
|
ns: TokenNamespace,
|
||||||
|
token_id: TokenId,
|
||||||
|
type: TokenType,
|
||||||
|
data: string | null,
|
||||||
|
): Promise<void>;
|
||||||
|
|
||||||
|
|
||||||
|
getToken(
|
||||||
|
doc_id: DocumentId,
|
||||||
|
ns: TokenNamespace,
|
||||||
|
token_id: TokenId
|
||||||
|
): Promise<TokenData|null>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token status types as stored in the database
|
||||||
|
*/
|
||||||
|
export type TokenType = 'ACTIVE' | 'DONE' | 'ACCEPTED' | 'DEAD';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of a Mongo query
|
||||||
|
*
|
||||||
|
* The returned property depends on the actual query.
|
||||||
|
*/
|
||||||
|
export interface TokenQueryResult
|
||||||
|
{
|
||||||
|
readonly [propName: string]: TokenNamespaceResults | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token data for requested namespaces
|
||||||
|
*/
|
||||||
|
export interface TokenNamespaceResults
|
||||||
|
{
|
||||||
|
readonly [propName: string]: TokenNamespaceData | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token data associated with the given namespace
|
||||||
|
*
|
||||||
|
* This contains duplicate information in order to work around inconvenient
|
||||||
|
* limitations in [earlier] versions of Mongo.
|
||||||
|
*/
|
||||||
|
export interface TokenNamespaceData
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Identifier of last token touched in this namespace
|
||||||
|
*/
|
||||||
|
readonly last: TokenId,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Most recent token status
|
||||||
|
*
|
||||||
|
* This is a duplicate of the last entry in `TokenEntry#statusLog`.
|
||||||
|
*/
|
||||||
|
readonly lastStatus: TokenStatus,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokens indexed by identifier
|
||||||
|
*
|
||||||
|
* These data are inconveniently placed---the type definition here is to
|
||||||
|
* accommodate the above fields. Anything using this should cast to
|
||||||
|
* `TokenEntry`.
|
||||||
|
*/
|
||||||
|
readonly [propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a given token
|
||||||
|
*/
|
||||||
|
export interface TokenEntry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Current token status
|
||||||
|
*
|
||||||
|
* This is a duplicate of the last element of `statusLog`.
|
||||||
|
*/
|
||||||
|
readonly status: TokenStatus,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log of all past status changes and any associated data
|
||||||
|
*
|
||||||
|
* This is pushed to on each status change. The last element is
|
||||||
|
* duplicated in `status`.
|
||||||
|
*/
|
||||||
|
readonly statusLog: TokenStatus[],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status of the token (past or present)
|
||||||
|
*
|
||||||
|
* A status is a `TokenType`, along with a timestamp of occurrence and
|
||||||
|
* optional data.
|
||||||
|
*/
|
||||||
|
export interface TokenStatus
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* State of the token
|
||||||
|
*/
|
||||||
|
readonly type: TokenType,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unix timestamp representing when the status change occurred
|
||||||
|
*/
|
||||||
|
readonly timestamp: UnixTimestamp,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Arbitrary data associated with the status change
|
||||||
|
*
|
||||||
|
* For example, a token of status `DONE` may be associated with the
|
||||||
|
* fulfillment of a request, in which case this may contain the response
|
||||||
|
* data.
|
||||||
|
*/
|
||||||
|
readonly data: string | null,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Token information
|
* Token information
|
||||||
*/
|
*/
|
||||||
|
@ -40,238 +167,3 @@ export interface TokenData
|
||||||
id: TokenId,
|
id: TokenId,
|
||||||
status: TokenStatus,
|
status: TokenStatus,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Manages token updates
|
|
||||||
*
|
|
||||||
* Note that this is tightly coupled with MongoDB.
|
|
||||||
*/
|
|
||||||
export default class TokenDao
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Mongo database collection
|
|
||||||
*/
|
|
||||||
private readonly _collection: MongoCollection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Field storing token data, relative to document root
|
|
||||||
*/
|
|
||||||
private readonly _rootField: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve a Unix timestamp
|
|
||||||
*
|
|
||||||
* This is used for timestampping token updates.
|
|
||||||
*/
|
|
||||||
private readonly _getTimestamp: () => UnixTimestamp;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize connection
|
|
||||||
*
|
|
||||||
* @param collection Mongo collection
|
|
||||||
* @param root_field topmost field in mongo document
|
|
||||||
* @param date_ctor Date constructor
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
collection: MongoCollection,
|
|
||||||
root_field: string,
|
|
||||||
getTimestamp: () => UnixTimestamp,
|
|
||||||
)
|
|
||||||
{
|
|
||||||
this._collection = collection;
|
|
||||||
this._rootField = root_field;
|
|
||||||
this._getTimestamp = getTimestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create or update a token record
|
|
||||||
*
|
|
||||||
* The token entry is entered in the token log, and then the current
|
|
||||||
* entry is updated to reflect the changes. The operation is atomic.
|
|
||||||
*
|
|
||||||
* @param doc_id unique document identifier
|
|
||||||
* @param ns token namespace
|
|
||||||
* @param token token value
|
|
||||||
* @param data token data, if any
|
|
||||||
* @param status arbitrary token type
|
|
||||||
*/
|
|
||||||
updateToken(
|
|
||||||
doc_id: DocumentId,
|
|
||||||
ns: TokenNamespace,
|
|
||||||
token_id: TokenId,
|
|
||||||
type: TokenType,
|
|
||||||
data: string | null,
|
|
||||||
): Promise<void>
|
|
||||||
{
|
|
||||||
const root = this._genRoot( ns ) + '.';
|
|
||||||
|
|
||||||
const token_entry: TokenStatus = {
|
|
||||||
type: type,
|
|
||||||
timestamp: this._getTimestamp(),
|
|
||||||
data: data,
|
|
||||||
};
|
|
||||||
|
|
||||||
const token_data = {
|
|
||||||
[ root + 'last' ]: token_id,
|
|
||||||
[ root + 'lastStatus' ]: token_entry,
|
|
||||||
[ root + token_id + '.status' ]: token_entry,
|
|
||||||
};
|
|
||||||
|
|
||||||
const token_log = {
|
|
||||||
[ root + token_id + '.statusLog' ]: token_entry,
|
|
||||||
};
|
|
||||||
|
|
||||||
return new Promise( ( resolve, reject ) =>
|
|
||||||
{
|
|
||||||
this._collection.update(
|
|
||||||
{ id: +doc_id },
|
|
||||||
{
|
|
||||||
$set: token_data,
|
|
||||||
$push: token_log
|
|
||||||
},
|
|
||||||
{ upsert: true },
|
|
||||||
|
|
||||||
function ( err: Error|null )
|
|
||||||
{
|
|
||||||
if ( err )
|
|
||||||
{
|
|
||||||
reject( err );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve existing token under the namespace NS, if any, for the doc
|
|
||||||
* identified by DOC_ID
|
|
||||||
*
|
|
||||||
* If a TOKEN_ID is provided, only that token will be queried; otherwise,
|
|
||||||
* the most recently created token will be the subject of the query.
|
|
||||||
*
|
|
||||||
* @param doc_id document identifier
|
|
||||||
* @param ns token namespace
|
|
||||||
* @param token_id token identifier (unique to NS)
|
|
||||||
*
|
|
||||||
* @return token data
|
|
||||||
*/
|
|
||||||
getToken( doc_id: DocumentId, ns: TokenNamespace, token_id: TokenId ):
|
|
||||||
Promise<TokenData|null>
|
|
||||||
{
|
|
||||||
const root = this._genRoot( ns ) + '.';
|
|
||||||
const fields: any = {};
|
|
||||||
|
|
||||||
fields[ root + 'last' ] = 1;
|
|
||||||
fields[ root + 'lastStatus' ] = 1;
|
|
||||||
|
|
||||||
if ( token_id )
|
|
||||||
{
|
|
||||||
// XXX: injectable
|
|
||||||
fields[ root + token_id ] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Promise( ( resolve, reject ) =>
|
|
||||||
{
|
|
||||||
this._collection.findOne(
|
|
||||||
{ id: +doc_id },
|
|
||||||
{ fields: fields },
|
|
||||||
( err: Error|null, data: TokenQueryResult ) =>
|
|
||||||
{
|
|
||||||
if ( err || !data )
|
|
||||||
{
|
|
||||||
reject( err );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const field = <TokenNamespaceResults>data[ this._rootField ]
|
|
||||||
|| {};
|
|
||||||
|
|
||||||
if ( !field[ ns ] )
|
|
||||||
{
|
|
||||||
resolve( null );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ns_data = <TokenNamespaceData>field[ ns ];
|
|
||||||
|
|
||||||
resolve( ( token_id )
|
|
||||||
? this._getRequestedToken( token_id, ns_data )
|
|
||||||
: this._getLatestToken( ns_data )
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} );
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve latest token data, or `null` if none
|
|
||||||
*
|
|
||||||
* @param ns_data namespace data
|
|
||||||
*
|
|
||||||
* @return data of latest token in namespace
|
|
||||||
*/
|
|
||||||
private _getLatestToken( ns_data: TokenNamespaceData ): TokenData | null
|
|
||||||
{
|
|
||||||
var last = ns_data.last;
|
|
||||||
|
|
||||||
if ( !last )
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: last,
|
|
||||||
status: ns_data.lastStatus,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve latest token data, or `null` if none
|
|
||||||
*
|
|
||||||
* @param token_id token identifier for namespace associated with NS_DATA
|
|
||||||
* @param ns_data namespace data
|
|
||||||
*
|
|
||||||
* @return data of requested token
|
|
||||||
*/
|
|
||||||
private _getRequestedToken(
|
|
||||||
token_id: TokenId,
|
|
||||||
ns_data: TokenNamespaceData
|
|
||||||
): TokenData | null
|
|
||||||
{
|
|
||||||
const reqtok = <TokenEntry>ns_data[ <string>token_id ];
|
|
||||||
|
|
||||||
if ( !reqtok )
|
|
||||||
{
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: token_id,
|
|
||||||
status: reqtok.status,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine token root for the given namespace
|
|
||||||
*
|
|
||||||
* @param ns token namespace
|
|
||||||
*
|
|
||||||
* @return token root for namespace NS
|
|
||||||
*/
|
|
||||||
private _genRoot( ns: TokenNamespace ): string
|
|
||||||
{
|
|
||||||
// XXX: injectable
|
|
||||||
return this._rootField + '.' + ns;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
/**
|
|
||||||
* Result of querying for a token from Mongo
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010-2019 R-T Specialty, LLC.
|
|
||||||
*
|
|
||||||
* This file is part of the Liza Data Collection Framework.
|
|
||||||
*
|
|
||||||
* liza is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as
|
|
||||||
* published by the Free Software Foundation, either version 3 of the
|
|
||||||
* License, or (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*
|
|
||||||
* These types are used to describe the structure of the token data as it
|
|
||||||
* is stored in Mongo. It has a number of undesirable properties and
|
|
||||||
* duplicates data---this was intended to make querying easier and work
|
|
||||||
* around Mongo limitations.
|
|
||||||
*
|
|
||||||
* This structure can be changed in the future, but we'll need to maintain
|
|
||||||
* compatibility with the existing data.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { TokenId } from "./Token";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token status types as stored in the database
|
|
||||||
*/
|
|
||||||
export type TokenType = 'ACTIVE' | 'DONE' | 'ACCEPTED' | 'DEAD';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Result of a Mongo query
|
|
||||||
*
|
|
||||||
* The returned property depends on the actual query.
|
|
||||||
*/
|
|
||||||
export interface TokenQueryResult
|
|
||||||
{
|
|
||||||
readonly [propName: string]: TokenNamespaceResults | null,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token data for requested namespaces
|
|
||||||
*/
|
|
||||||
export interface TokenNamespaceResults
|
|
||||||
{
|
|
||||||
readonly [propName: string]: TokenNamespaceData | null,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Token data associated with the given namespace
|
|
||||||
*
|
|
||||||
* This contains duplicate information in order to work around inconvenient
|
|
||||||
* limitations in [earlier] versions of Mongo.
|
|
||||||
*/
|
|
||||||
export interface TokenNamespaceData
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Identifier of last token touched in this namespace
|
|
||||||
*/
|
|
||||||
readonly last: TokenId,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Most recent token status
|
|
||||||
*
|
|
||||||
* This is a duplicate of the last entry in `TokenEntry#statusLog`.
|
|
||||||
*/
|
|
||||||
readonly lastStatus: TokenStatus,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tokens indexed by identifier
|
|
||||||
*
|
|
||||||
* These data are inconveniently placed---the type definition here is to
|
|
||||||
* accommodate the above fields. Anything using this should cast to
|
|
||||||
* `TokenEntry`.
|
|
||||||
*/
|
|
||||||
readonly [propName: string]: TokenEntry | TokenStatus | TokenId | null,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about a given token
|
|
||||||
*/
|
|
||||||
export interface TokenEntry
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Current token status
|
|
||||||
*
|
|
||||||
* This is a duplicate of the last element of `statusLog`.
|
|
||||||
*/
|
|
||||||
readonly status: TokenStatus,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log of all past status changes and any associated data
|
|
||||||
*
|
|
||||||
* This is pushed to on each status change. The last element is
|
|
||||||
* duplicated in `status`.
|
|
||||||
*/
|
|
||||||
readonly statusLog: TokenStatus[],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Status of the token (past or present)
|
|
||||||
*
|
|
||||||
* A status is a `TokenType`, along with a timestamp of occurrence and
|
|
||||||
* optional data.
|
|
||||||
*/
|
|
||||||
export interface TokenStatus
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* State of the token
|
|
||||||
*/
|
|
||||||
readonly type: TokenType,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unix timestamp representing when the status change occurred
|
|
||||||
*/
|
|
||||||
readonly timestamp: UnixTimestamp,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Arbitrary data associated with the status change
|
|
||||||
*
|
|
||||||
* For example, a token of status `DONE` may be associated with the
|
|
||||||
* fulfillment of a request, in which case this may contain the response
|
|
||||||
* data.
|
|
||||||
*/
|
|
||||||
readonly data: string | null,
|
|
||||||
}
|
|
|
@ -20,15 +20,13 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
TokenData,
|
||||||
TokenQueryResult,
|
TokenQueryResult,
|
||||||
TokenStatus,
|
TokenStatus,
|
||||||
} from "../../../src/server/token/TokenQueryResult";
|
|
||||||
|
|
||||||
import {
|
|
||||||
default as Sut,
|
|
||||||
TokenData,
|
|
||||||
} from "../../../src/server/token/TokenDao";
|
} from "../../../src/server/token/TokenDao";
|
||||||
|
|
||||||
|
import { MongoTokenDao as Sut } from "../../../src/server/token/MongoTokenDao";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
TokenId,
|
TokenId,
|
||||||
TokenNamespace,
|
TokenNamespace,
|
Loading…
Reference in New Issue