diff --git a/test/test-cases/api-sync-mode-sqlite-test-cases.js b/test/test-cases/api-sync-mode-sqlite-test-cases.js index fb3ad4784..70650811b 100644 --- a/test/test-cases/api-sync-mode-sqlite-test-cases.js +++ b/test/test-cases/api-sync-mode-sqlite-test-cases.js @@ -31,6 +31,24 @@ module.exports = ( } = params const auth = { token: '' } + it('it should be successfully performed by the isStagingBfxApi method', async function () { + this.timeout(5000) + + const res = await agent + .post(`${basePath}/json-rpc`) + .type('json') + .send({ + method: 'isStagingBfxApi', + id: 5 + }) + .expect('Content-Type', /json/) + .expect(200) + + assert.isObject(res.body) + assert.propertyVal(res.body, 'id', 5) + assert.isBoolean(res.body.result) + }) + it('it should be successfully performed by the getPlatformStatus method', async function () { this.timeout(5000) @@ -184,6 +202,7 @@ module.exports = ( assert.isNull(res.body.result.authTokenTTLSec) assert.isNull(res.body.result.localUsername) assert.isNull(res.body.result.lastSyncMts) + assert.isBoolean(res.body.result.isStagingBfxApi) auth.token = res.body.result.token }) @@ -283,6 +302,7 @@ module.exports = ( assert.isNumber(res.body.result.authTokenTTLSec) assert.isNull(res.body.result.localUsername) assert.isNull(res.body.result.lastSyncMts) + assert.isBoolean(res.body.result.isStagingBfxApi) }) it('it should not be successfully performed by the signIn method', async function () { @@ -483,6 +503,7 @@ module.exports = ( assert.isBoolean(user.isRestrictedToBeAddedToSubAccount) assert.isBoolean(user.isApiKeysAuth) assert.isArray(user.subUsers) + assert.isBoolean(user.isStagingBfxApi) user.subUsers.forEach((subUser) => { assert.isString(subUser.email) diff --git a/workers/loc.api/service.report.framework.js b/workers/loc.api/service.report.framework.js index c6f716194..6878b21f3 100644 --- a/workers/loc.api/service.report.framework.js +++ b/workers/loc.api/service.report.framework.js @@ -98,6 +98,12 @@ class FrameworkReportService extends ReportService { }, 'verifyOnBFX', args, cb) } + isStagingBfxApi (space, args, cb) { + return this._responder(() => { + return this._authenticator.isStagingBfxApi() + }, 'isStagingBfxApi', args, cb) + } + signUp (space, args, cb) { return this._responder(() => { return this._authenticator.signUp(args) @@ -114,7 +120,8 @@ class FrameworkReportService extends ReportService { shouldNotSyncOnStartupAfterUpdate, isSyncOnStartupRequired, authTokenTTLSec, - localUsername + localUsername, + isStagingBfxApi } = await this._authenticator.signIn( args, { isReturnedUser: true } @@ -148,7 +155,8 @@ class FrameworkReportService extends ReportService { shouldNotSyncOnStartupAfterUpdate, authTokenTTLSec, localUsername, - lastSyncMts: lastFinishedSyncQueueJob?.updatedAt ?? null + lastSyncMts: lastFinishedSyncQueueJob?.updatedAt ?? null, + isStagingBfxApi } }, 'signIn', args, cb) } @@ -208,7 +216,8 @@ class FrameworkReportService extends ReportService { 'isNotProtected', 'subUsers', 'isRestrictedToBeAddedToSubAccount', - 'isApiKeysAuth' + 'isApiKeysAuth', + 'isStagingBfxApi' ] } ) diff --git a/workers/loc.api/sync/authenticator/helpers/pick-session-props.js b/workers/loc.api/sync/authenticator/helpers/pick-session-props.js index 9f538fe20..9204a11d7 100644 --- a/workers/loc.api/sync/authenticator/helpers/pick-session-props.js +++ b/workers/loc.api/sync/authenticator/helpers/pick-session-props.js @@ -26,6 +26,7 @@ module.exports = (session, isReturnedPassword) => { 'shouldNotSyncOnStartupAfterUpdate', 'isSyncOnStartupRequired', 'authTokenTTLSec', + 'isStagingBfxApi', ...passwordProp ] const { subUsers: reqSubUsers } = { ...session } diff --git a/workers/loc.api/sync/authenticator/index.js b/workers/loc.api/sync/authenticator/index.js index 718290be0..dbe81c3fe 100644 --- a/workers/loc.api/sync/authenticator/index.js +++ b/workers/loc.api/sync/authenticator/index.js @@ -39,7 +39,8 @@ const depsTypes = (TYPES) => [ TYPES.Crypto, TYPES.SyncFactory, TYPES.WSEventEmitterFactory, - TYPES.Logger + TYPES.Logger, + TYPES.CONF ] class Authenticator { constructor ( @@ -50,7 +51,8 @@ class Authenticator { crypto, syncFactory, wsEventEmitterFactory, - logger + logger, + conf ) { this.dao = dao this.TABLES_NAMES = TABLES_NAMES @@ -60,6 +62,7 @@ class Authenticator { this.syncFactory = syncFactory this.wsEventEmitterFactory = wsEventEmitterFactory this.logger = logger + this.conf = conf /** * It may only work for one grenache worker instance @@ -80,6 +83,12 @@ class Authenticator { this.authTokenInvalidateIntervalsSec = 10 * 60 } + isStagingBfxApi () { + const bfxApiUrl = this.conf?.restUrl ?? '' + + return /staging/gi.test(bfxApiUrl) + } + async signUp (args, opts) { const { auth, params } = args ?? {} const { @@ -216,7 +225,8 @@ class Authenticator { shouldNotSyncOnStartupAfterUpdate: 0, isSyncOnStartupRequired: 0, authTokenTTLSec, - localUsername + localUsername, + isStagingBfxApi: this.isStagingBfxApi() }, { isNotInTrans, withWorkerThreads, doNotQueueQuery } ) @@ -332,17 +342,21 @@ class Authenticator { userData = await this.rService._checkAuthInApi({ auth: { authToken: newAuthToken, apiKey, apiSecret } }) + userData.isStagingBfxApi = this.isStagingBfxApi() } catch (err) { if (!isENetError(err)) { throw err } + + this.logger.debug(err) } const { id, timezone, username: uName, - email: emailFromApi + email: emailFromApi, + isStagingBfxApi } = userData ?? {} const username = generateSubUserName( { username: uName }, @@ -360,6 +374,7 @@ class Authenticator { isDataFromDb: isDataFromDb === null ? user.isDataFromDb : isDataFromDb, + isStagingBfxApi, ...newAuthToken ? { authToken: newAuthToken } : null @@ -376,6 +391,20 @@ class Authenticator { { withWorkerThreads, doNotQueueQuery } ) + // Need to update `isStagingBfxApi` flag for sub-users if `isSubAccount` + if ( + isSubAccount && + Array.isArray(user?.subUsers) && + user.subUsers.length > 0 + ) { + await this.dao.updateCollBy( + this.TABLES_NAMES.USERS, + { $in: { _id: user.subUsers.map(({ _id }) => (_id)) } }, + { isStagingBfxApi }, + { withWorkerThreads, doNotQueueQuery } + ) + } + if (res && res.changes < 1) { throw new AuthError() } diff --git a/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v29.js b/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v29.js deleted file mode 100644 index 5f743bf0c..000000000 --- a/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v29.js +++ /dev/null @@ -1,83 +0,0 @@ -'use strict' - -const AbstractMigration = require('./abstract.migration') -const { getSqlArrToModifyColumns } = require('./helpers') - -class MigrationV29 extends AbstractMigration { - /** - * @override - */ - up () { - const sqlArr = [ - 'ALTER TABLE orders ADD COLUMN routing VARCHAR(255)', - 'ALTER TABLE orders ADD COLUMN meta TEXT', - 'DELETE FROM orders', - `DELETE FROM completedOnFirstSyncColls - WHERE collName = '_getOrders'` - ] - - this.addSql(sqlArr) - } - - /** - * @override - */ - beforeDown () { return this.dao.disableForeignKeys() } - - /** - * @override - */ - down () { - const sqlArr = [ - ...getSqlArrToModifyColumns( - 'orders', - { - _id: 'INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT', - id: 'BIGINT', - gid: 'BIGINT', - cid: 'BIGINT', - symbol: 'VARCHAR(255)', - mtsCreate: 'BIGINT', - mtsUpdate: 'BIGINT', - amount: 'DECIMAL(22,12)', - amountOrig: 'DECIMAL(22,12)', - type: 'VARCHAR(255)', - typePrev: 'VARCHAR(255)', - flags: 'INT', - status: 'VARCHAR(255)', - price: 'DECIMAL(22,12)', - priceAvg: 'DECIMAL(22,12)', - priceTrailing: 'DECIMAL(22,12)', - priceAuxLimit: 'DECIMAL(22,12)', - notify: 'INT', - placedId: 'BIGINT', - _lastAmount: 'DECIMAL(22,12)', - amountExecuted: 'DECIMAL(22,12)', - subUserId: 'INT', - user_id: 'INT NOT NULL', - __constraints__: [ - `CONSTRAINT #{tableName}_fk_user_id - FOREIGN KEY (user_id) - REFERENCES users(_id) - ON UPDATE CASCADE - ON DELETE CASCADE`, - `CONSTRAINT #{tableName}_fk_subUserId - FOREIGN KEY (subUserId) - REFERENCES users(_id) - ON UPDATE CASCADE - ON DELETE CASCADE` - ] - } - ) - ] - - this.addSql(sqlArr) - } - - /** - * @override - */ - afterDown () { return this.dao.enableForeignKeys() } -} - -module.exports = MigrationV29 diff --git a/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v39.1701950278668.js b/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v39.1701950278668.js new file mode 100644 index 000000000..effc2a66f --- /dev/null +++ b/workers/loc.api/sync/dao/db-migrations/sqlite-migrations/migration.v39.1701950278668.js @@ -0,0 +1,75 @@ +'use strict' + +/* + * CREATED_AT: 2023-12-07T11:57:58.668Z + * VERSION: v39 + */ + +const { + ID_PRIMARY_KEY +} = require('./helpers/const') +const { + getSqlArrToModifyColumns +} = require('./helpers') + +const AbstractMigration = require('./abstract.migration') + +class MigrationV39 extends AbstractMigration { + /** + * @override + */ + up () { + const sqlArr = [ + 'ALTER TABLE users ADD COLUMN isStagingBfxApi INT' + ] + + this.addSql(sqlArr) + } + + /** + * @override + */ + down () { + const sqlArr = [ + ...getSqlArrToModifyColumns( + 'users', + { + _id: ID_PRIMARY_KEY, + id: 'BIGINT', + email: 'VARCHAR(255)', + apiKey: 'VARCHAR(255)', + apiSecret: 'VARCHAR(255)', + authToken: 'VARCHAR(255)', + active: 'INT', + isDataFromDb: 'INT', + timezone: 'VARCHAR(255)', + username: 'VARCHAR(255)', + localUsername: 'VARCHAR(255)', + passwordHash: 'VARCHAR(255)', + isNotProtected: 'INT', + isSubAccount: 'INT', + isSubUser: 'INT', + shouldNotSyncOnStartupAfterUpdate: 'INT', + isSyncOnStartupRequired: 'INT', + authTokenTTLSec: 'INT', + createdAt: 'BIGINT', + updatedAt: 'BIGINT' + } + ) + ] + + this.addSql(sqlArr) + } + + /** + * @override + */ + beforeDown () { return this.dao.disableForeignKeys() } + + /** + * @override + */ + afterDown () { return this.dao.enableForeignKeys() } +} + +module.exports = MigrationV39 diff --git a/workers/loc.api/sync/dao/helpers/users/normalize-user-data.js b/workers/loc.api/sync/dao/helpers/users/normalize-user-data.js index 1e44b0afe..bbd36cc9c 100644 --- a/workers/loc.api/sync/dao/helpers/users/normalize-user-data.js +++ b/workers/loc.api/sync/dao/helpers/users/normalize-user-data.js @@ -9,7 +9,8 @@ const _normalize = (userData) => { haveSubUsers, isNotProtected, shouldNotSyncOnStartupAfterUpdate, - isSyncOnStartupRequired + isSyncOnStartupRequired, + isStagingBfxApi } = userData ?? {} return { @@ -21,7 +22,8 @@ const _normalize = (userData) => { haveSubUsers: !!haveSubUsers, isNotProtected: !!isNotProtected, shouldNotSyncOnStartupAfterUpdate: !!shouldNotSyncOnStartupAfterUpdate, - isSyncOnStartupRequired: !!isSyncOnStartupRequired + isSyncOnStartupRequired: !!isSyncOnStartupRequired, + isStagingBfxApi: !!isStagingBfxApi } } diff --git a/workers/loc.api/sync/schema/models.js b/workers/loc.api/sync/schema/models.js index ad1f74536..8bb96494c 100644 --- a/workers/loc.api/sync/schema/models.js +++ b/workers/loc.api/sync/schema/models.js @@ -8,7 +8,7 @@ * e.g. `migration.v1.js`, where `v1` is `SUPPORTED_DB_VERSION` */ -const SUPPORTED_DB_VERSION = 38 +const SUPPORTED_DB_VERSION = 39 const TABLES_NAMES = require('./tables-names') const { @@ -67,6 +67,7 @@ const _models = new Map([ shouldNotSyncOnStartupAfterUpdate: 'INT', isSyncOnStartupRequired: 'INT', authTokenTTLSec: 'INT', + isStagingBfxApi: 'INT', createdAt: 'BIGINT', updatedAt: 'BIGINT',