Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/multi query management #255

Open
wants to merge 15 commits into
base: develop
Choose a base branch
from
90 changes: 66 additions & 24 deletions HTTPServer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import { LoginController } from './controllers/LoginController.mjs';
import { SessionController } from './controllers/SessionController.mjs';
import { UserAPIController } from './controllers/UserAPIController.mjs';

// Adapters
import { TranslatorServicexFEAdapter } from './adapters/TranslatorServicexFEAdapter.mjs';
import { QueryServicexFEAdapter } from './adapters/QueryServicexFEAdapter.mjs';

export function startServer(config, services) {

const filters = {whitelistRx: /^ara-/}; // TODO: move to config file
Expand All @@ -25,15 +29,24 @@ export function startServer(config, services) {
const authService = services.authService;
const userService = services.userService;
const translatorService = services.translatorService;
const queryService = services.queryService;
const demoQueries = config.frontend.cached_queries.filter(e => e.allow_inbound);
const translatorServicexFEAdapter = new TranslatorServicexFEAdapter();
const queryServicexFEAdapter = new QueryServicexFEAdapter();
const __root = path.dirname(url.fileURLToPath(import.meta.url));
const app = express();
const loginController = new LoginController(config, authService);
const queryAPIController = new QueryAPIController(config, translatorService, filters);
const queryAPIController = new QueryAPIController(config,
translatorService,
translatorServicexFEAdapter,
queryService,
queryServicexFEAdapter,
userService,
filters);
const configAPIController = new ConfigAPIController(config);
const userAPIController = new UserAPIController(config, userService, translatorService);
const sessionController = new SessionController(config, authService);
const API_PATH_PREFIX = '/api/v1';
const API_PATH_V1 = '/api/v1';
const SITE_PATH_PREFIX = '';
app.use(pinoHttp({logger: logger}));
app.use(express.json({ limit: config.json_payload_limit }));
Expand All @@ -59,8 +72,8 @@ export function startServer(config, services) {
app.use(sessionController.attachSessionData.bind(sessionController));

// Session status API
app.get(`${API_PATH_PREFIX}/session/status`, sessionController.getStatus.bind(sessionController));
app.post(`${API_PATH_PREFIX}/session/status`, sessionController.updateStatus.bind(sessionController));
app.get(`${API_PATH_V1}/session/status`, sessionController.getStatus.bind(sessionController));
app.post(`${API_PATH_V1}/session/status`, sessionController.updateStatus.bind(sessionController));

// Login/logout
app.get('/oauth2/redir/:provider', loginController.authRedir.bind(loginController));
Expand All @@ -75,32 +88,61 @@ export function startServer(config, services) {
* to explicit user activity (e.g., the FE app needs this at startup regardless of whether there is a user
* session active). To maintain consistency with the decision that only intentional user activity should count
* towards updating session last-touch times, we therefore exclude this API from that set. */
app.use(`${API_PATH_PREFIX}/config`, configAPIController.getConfig.bind(configAPIController));
app.use(`${API_PATH_V1}/config`, configAPIController.getConfig.bind(configAPIController));

/** All routes below this point MUST use one of authenticate[Un]PrivilegedRequest() **/

// Query routes: unprivileged
app.use(`${API_PATH_PREFIX}/query`, sessionController.authenticateUnprivilegedRequest.bind(sessionController));
app.post(`${API_PATH_PREFIX}/query`, queryAPIController.submitQuery.bind(queryAPIController));
app.get(`${API_PATH_PREFIX}/query/:qid/status`, queryAPIController.getQueryStatus.bind(queryAPIController));
app.get(`${API_PATH_PREFIX}/query/:qid/result`, queryAPIController.getQueryResult.bind(queryAPIController));
// Submit query route: privileged session
app.post(`${API_PATH_V1}/query`,
sessionController.authenticatePrivilegedRequest.bind(sessionController),
queryAPIController.submitQuery.bind(queryAPIController));
gprice1129 marked this conversation as resolved.
Show resolved Hide resolved
// Query request routes: unprivileged session
app.get(`${API_PATH_V1}/query/:qid/status`,
sessionController.authenticateUnprivilegedRequest.bind(sessionController),
queryAPIController.getQueryStatus.bind(queryAPIController));
app.get(`${API_PATH_V1}/query/:qid/result`,
sessionController.authenticateUnprivilegedRequest.bind(sessionController),
queryAPIController.getQueryResult.bind(queryAPIController));
gprice1129 marked this conversation as resolved.
Show resolved Hide resolved
// Query callback route: behind hmac
app.post(`${API_PATH_V1}/query/update`, queryAPIController.updateQuery.bind(queryAPIController));

// User routes: privileged
app.use(`${API_PATH_PREFIX}/users`, sessionController.authenticatePrivilegedRequest.bind(sessionController));
app.get(`${API_PATH_PREFIX}/users/me`, userAPIController.getUser.bind(userAPIController));
app.get(`${API_PATH_PREFIX}/users/me/preferences`, userAPIController.getUserPrefs.bind(userAPIController));
app.post(`${API_PATH_PREFIX}/users/me/preferences`, userAPIController.updateUserPrefs.bind(userAPIController));
app.get(`${API_PATH_PREFIX}/users/me/saves`, userAPIController.getUserSaves.bind(userAPIController));
app.post(`${API_PATH_PREFIX}/users/me/saves`, userAPIController.updateUserSaves.bind(userAPIController));
app.get(`${API_PATH_PREFIX}/users/me/saves/:save_id`, userAPIController.getUserSaveById.bind(userAPIController));
app.put(`${API_PATH_PREFIX}/users/me/saves/:save_id`, userAPIController.updateUserSaveById.bind(userAPIController));
app.delete(`${API_PATH_PREFIX}/users/me/saves/:save_id`, userAPIController.deleteUserSaveById.bind(userAPIController));
app.use(`${API_PATH_V1}/users`, sessionController.authenticatePrivilegedRequest.bind(sessionController));
app.get(`${API_PATH_V1}/users/me`, userAPIController.getUser.bind(userAPIController));
app.get(`${API_PATH_V1}/users/me/preferences`, userAPIController.getUserPrefs.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/preferences`, userAPIController.updateUserPrefs.bind(userAPIController));
app.use(`${API_PATH_V1}/users`, sessionController.authenticatePrivilegedRequest.bind(sessionController));

// User queries
// Creation of user queries is done on submission. See the /query endpoint
app.get(`${API_PATH_V1}/users/me/queries`, userAPIController.getUserQueries.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/queries/:save_id`, userAPIController.updateUserSaveById.bind(userAPIController));
app.delete(`${API_PATH_V1}/users/me/queries/:save_id`, userAPIController.deleteUserSaveById.bind(userAPIController));

// User bookmarks
app.get(`${API_PATH_V1}/users/me/bookmarks`, userAPIController.getUserBookmarks.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/bookmarks`, userAPIController.updateUserSaves.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/bookmarks/:save_id`, userAPIController.updateUserSaveById.bind(userAPIController));
app.delete(`${API_PATH_V1}/users/me/bookmarks/:save_id`, userAPIController.deleteUserSaveById.bind(userAPIController));

// User tags
app.get(`${API_PATH_V1}/users/me/tags`, userAPIController.getUserTags.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/tags`, userAPIController.updateUserSaves.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/tags/:save_id`, userAPIController.updateUserSaveById.bind(userAPIController));
app.delete(`${API_PATH_V1}/users/me/tags/:save_id`, userAPIController.deleteUserSaveById.bind(userAPIController));

// User saves
app.get(`${API_PATH_V1}/users/me/saves`, userAPIController.getUserSaves.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/saves`, userAPIController.updateUserSaves.bind(userAPIController));
app.get(`${API_PATH_V1}/users/me/saves/:save_id`, userAPIController.getUserSaveById.bind(userAPIController));
app.put(`${API_PATH_V1}/users/me/saves/:save_id`, userAPIController.updateUserSaveById.bind(userAPIController));
app.delete(`${API_PATH_V1}/users/me/saves/:save_id`, userAPIController.deleteUserSaveById.bind(userAPIController));
// workspaces
app.get(`${API_PATH_PREFIX}/users/me/workspaces`, userAPIController.getUserWorkspaces.bind(userAPIController));
app.get(`${API_PATH_PREFIX}/users/me/workspaces/:ws_id`, userAPIController.getUserWorkspaceById.bind(userAPIController));
app.post(`${API_PATH_PREFIX}/users/me/workspaces`, userAPIController.createUserWorkspace.bind(userAPIController));
app.put(`${API_PATH_PREFIX}/users/me/workspaces/:ws_id`, userAPIController.updateUserWorkspace.bind(userAPIController));
app.delete(`${API_PATH_PREFIX}/users/me/workspaces/:ws_id`, userAPIController.deleteUserWorkspace.bind(userAPIController));
app.get(`${API_PATH_V1}/users/me/workspaces`, userAPIController.getUserWorkspaces.bind(userAPIController));
app.get(`${API_PATH_V1}/users/me/workspaces/:ws_id`, userAPIController.getUserWorkspaceById.bind(userAPIController));
app.post(`${API_PATH_V1}/users/me/workspaces`, userAPIController.createUserWorkspace.bind(userAPIController));
app.put(`${API_PATH_V1}/users/me/workspaces/:ws_id`, userAPIController.updateUserWorkspace.bind(userAPIController));
app.delete(`${API_PATH_V1}/users/me/workspaces/:ws_id`, userAPIController.deleteUserWorkspace.bind(userAPIController));

app.all(['/api', '/api/*'], (req, res) => {
return res.status(403).send('API action Forbidden');
Expand Down
19 changes: 15 additions & 4 deletions StartServer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,20 @@ import { loadBiolink } from './lib/biolink-model.mjs';
import { loadChebi } from './lib/chebi.mjs';
import { loadTrapi } from './lib/trapi.mjs';
import { TranslatorService } from './services/TranslatorService.mjs';
import { TranslatorServicexFEAdapter } from './adapters/TranslatorServicexFEAdapter.mjs';
import { ARSClient } from './lib/ARSClient.mjs';
import * as httpserver from './HTTPServer.mjs';
import { AuthService } from './services/AuthService.mjs';
import { UserService } from './services/UserService.mjs';
import { QueryService } from './services/QueryService.mjs';
import { ARSCallbackxQueryServiceAdapter } from './adapters/ARSCallbackxQueryServiceAdapter.mjs';

import { SessionStorePostgres } from './stores/SessionStorePostgres.mjs';
import { UserStorePostgres } from './stores/UserStorePostgres.mjs';
import { pg } from './lib/postgres_preamble.mjs';
import { UserPreferenceStorePostgres } from './stores/UserPreferenceStorePostgres.mjs';
import { UserSavedDataStorePostgres } from './stores/UserSavedDataStorePostgres.mjs';
import { UserWorkspaceStorePostgres } from './stores/UserWorkspaceStorePostgres.mjs';
import { QueryStorePostgres } from './stores/QueryStorePostgres.mjs';


// Load the config asap as basically everything depends on it
Expand Down Expand Up @@ -48,8 +50,7 @@ const TRANSLATOR_SERVICE = (function (config) {
config.ars_endpoint.post_uri,
config.ars_endpoint.retain_uri,
config.ars_endpoint.use_ars_merging);
const outputAdapter = new TranslatorServicexFEAdapter();
return new TranslatorService(queryClient, outputAdapter);
return new TranslatorService(queryClient);
})(SERVER_CONFIG);

// Bootstrap the auth service
Expand Down Expand Up @@ -83,10 +84,20 @@ const USER_SERVICE = (function (config) {
);
})(SERVER_CONFIG);

const QUERY_SERVICE = (function (config) {
const dbPool = new pg.Pool({
...config.storage.pg,
password: config.secrets.pg.password,
ssl: config.db_conn.ssl
});
return new QueryService(new QueryStorePostgres(dbPool),
new ARSCallbackxQueryServiceAdapter());
})(SERVER_CONFIG);
logger.info(SERVER_CONFIG, "Server configuration");

httpserver.startServer(SERVER_CONFIG, {
translatorService: TRANSLATOR_SERVICE,
authService: AUTH_SERVICE,
userService: USER_SERVICE
userService: USER_SERVICE,
queryService: QUERY_SERVICE
});
43 changes: 43 additions & 0 deletions adapters/ARSCallbackxQueryServiceAdapter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export { ARSCallbackxQueryServiceAdapter };
import { logger } from '../lib/logger.mjs';
import * as cmn from '../lib/common.mjs';

class ARSCallbackxQueryServiceAdapter {
processQueryUpdate(queryUpdate) {
const eventType = cmn.jsonGet(queryUpdate, 'event_type');
const update = {
pk: cmn.jsonGet(queryUpdate, 'pk'),
timestamp: cmn.jsonGet(queryUpdate, 'timestamp'),
status: null,
aras: []
};
switch (eventType) {
case _UPDATE_EVENT.MV_AVAILABLE:
update.aras = cmn.jsonGet(queryUpdate, 'merged_versions_list').map(idAraPair => idAraPair[1]);
update.aras.reverse();
if (cmn.jsonGet(queryUpdate, 'complete')) {
update.status = cmn.QUERY_STATUS.COMPLETE;
} else {
update.status = cmn.QUERY_STATUS.RUNNING;
}
break;
case _UPDATE_EVENT.MV_BEGUN:
case _UPDATE_EVENT.ARA_COMPLETE:
break; // We can ignore these events for now
case _UPDATE_EVENT.ERROR:
logger.error(`Received ARS callback error for query with PK ${update.pk}: ${queryUpdate.message}`);
update.status = cmn.QUERY_STATUS.ERROR;
break;
default:
throw new Error(`Unknown ARS callback event type: ${eventType}`);
}
return update;
}
}

const _UPDATE_EVENT = Object.freeze({
MV_AVAILABLE: 'merged_version_available',
MV_BEGUN: 'merged_version_begun',
ARA_COMPLETE: 'ara_response_complete',
ERROR: 'ars_error'
});
23 changes: 23 additions & 0 deletions adapters/QueryServicexFEAdapter.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
export { QueryServicexFEAdapter };

class QueryServicexFEAdapter {

querySubmitToFE(queryModel) {
return {
status: 'success',
data: queryModel.pk
}
}

queryStatusToFE(queryModel) {
return {
status: queryModel.status,
data: {
qid: queryModel.pk,
aras: queryModel.metadata.aras,
timestamp: queryModel.time_updated
}
};
}
}
File renamed without changes.
Loading