@toxplanet/pegasus-sdk 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/config/environment.acc.js +27 -0
- package/config/environment.dev.js +27 -0
- package/config/environment.prod.js +27 -0
- package/config/environment.qa.js +27 -0
- package/config/index.js +67 -0
- package/index.js +7 -0
- package/lib/chemicals.js +244 -8
- package/lib/connection.js +43 -26
- package/lib/db/index.js +11 -0
- package/lib/db/schema.js +28 -0
- package/lib/documents.js +48 -1
- package/lib/elasticsearch.js +404 -0
- package/lib/search.js +311 -8
- package/lib/sync.js +3 -1
- package/lib/utils.js +3 -1
- package/package.json +30 -5
- package/env.example +0 -3
- package/index.d.ts +0 -215
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
environment: 'acc',
|
|
3
|
+
region: 'us-east-1',
|
|
4
|
+
secretName: 'arn:aws:secretsmanager:us-east-1:292931567094:secret:rds!cluster-b851c3ce-58cc-41cd-aeae-05cc7f5e031a-ZYSjiI',
|
|
5
|
+
openSearchEndpoint: 'https://war8lk73nzswquk8dcz1.us-east-1.aoss.amazonaws.com',
|
|
6
|
+
openSearchIndex: 'chemicals',
|
|
7
|
+
database: {
|
|
8
|
+
host: 'cr-chemicals.cluster-cz0iqdg8irhb.us-east-1.rds.amazonaws.com',
|
|
9
|
+
name: 'chemicals'
|
|
10
|
+
},
|
|
11
|
+
postgres: {
|
|
12
|
+
maxConnections: 2,
|
|
13
|
+
minConnections: 0,
|
|
14
|
+
idleTimeoutMillis: 30000,
|
|
15
|
+
connectionTimeoutMillis: 5000,
|
|
16
|
+
statementTimeout: 30000,
|
|
17
|
+
queryTimeout: 30000,
|
|
18
|
+
ssl: {
|
|
19
|
+
rejectUnauthorized: false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
indexRoutes: {
|
|
23
|
+
chemicals: ['chemicals*'],
|
|
24
|
+
documents: ['documents*'],
|
|
25
|
+
search: [/^(chemicals|substances|search)/]
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
environment: 'dev',
|
|
3
|
+
region: 'us-east-1',
|
|
4
|
+
secretName: 'arn:aws:secretsmanager:us-east-1:292931567094:secret:rds!cluster-b851c3ce-58cc-41cd-aeae-05cc7f5e031a-ZYSjiI',
|
|
5
|
+
openSearchEndpoint: 'https://war8lk73nzswquk8dcz1.us-east-1.aoss.amazonaws.com',
|
|
6
|
+
openSearchIndex: 'chemicals',
|
|
7
|
+
database: {
|
|
8
|
+
host: 'cr-chemicals.cluster-cz0iqdg8irhb.us-east-1.rds.amazonaws.com',
|
|
9
|
+
name: 'chemicals'
|
|
10
|
+
},
|
|
11
|
+
postgres: {
|
|
12
|
+
maxConnections: 2,
|
|
13
|
+
minConnections: 0,
|
|
14
|
+
idleTimeoutMillis: 30000,
|
|
15
|
+
connectionTimeoutMillis: 5000,
|
|
16
|
+
statementTimeout: 30000,
|
|
17
|
+
queryTimeout: 30000,
|
|
18
|
+
ssl: {
|
|
19
|
+
rejectUnauthorized: false
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
indexRoutes: {
|
|
23
|
+
chemicals: ['chemicals*'],
|
|
24
|
+
documents: ['documents*'],
|
|
25
|
+
search: [/^(chemicals|substances|search)/]
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
environment: 'prod',
|
|
3
|
+
region: 'us-east-1',
|
|
4
|
+
secretName: 'pegasus/production/database',
|
|
5
|
+
openSearchEndpoint: null,
|
|
6
|
+
openSearchIndex: 'chemicals',
|
|
7
|
+
database: {
|
|
8
|
+
host: null,
|
|
9
|
+
name: 'chemicals'
|
|
10
|
+
},
|
|
11
|
+
postgres: {
|
|
12
|
+
maxConnections: 10,
|
|
13
|
+
minConnections: 2,
|
|
14
|
+
idleTimeoutMillis: 120000,
|
|
15
|
+
connectionTimeoutMillis: 15000,
|
|
16
|
+
statementTimeout: 120000,
|
|
17
|
+
queryTimeout: 120000,
|
|
18
|
+
ssl: {
|
|
19
|
+
rejectUnauthorized: true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
indexRoutes: {
|
|
23
|
+
chemicals: ['chemicals*'],
|
|
24
|
+
documents: ['documents*'],
|
|
25
|
+
search: [/^(chemicals|substances|search)/]
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
environment: 'qa',
|
|
3
|
+
region: 'us-east-1',
|
|
4
|
+
secretName: 'pegasus/qa/database',
|
|
5
|
+
openSearchEndpoint: null,
|
|
6
|
+
openSearchIndex: 'chemicals',
|
|
7
|
+
database: {
|
|
8
|
+
host: null,
|
|
9
|
+
name: 'chemicals'
|
|
10
|
+
},
|
|
11
|
+
postgres: {
|
|
12
|
+
maxConnections: 5,
|
|
13
|
+
minConnections: 1,
|
|
14
|
+
idleTimeoutMillis: 60000,
|
|
15
|
+
connectionTimeoutMillis: 10000,
|
|
16
|
+
statementTimeout: 60000,
|
|
17
|
+
queryTimeout: 60000,
|
|
18
|
+
ssl: {
|
|
19
|
+
rejectUnauthorized: true
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
indexRoutes: {
|
|
23
|
+
chemicals: ['chemicals*'],
|
|
24
|
+
documents: ['documents*'],
|
|
25
|
+
search: [/^(chemicals|substances|search)/]
|
|
26
|
+
}
|
|
27
|
+
};
|
package/config/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
const { logInfo } = require('@toxplanet/tphelper/logging');
|
|
2
|
+
|
|
3
|
+
function loadConfig(envOverride = null) {
|
|
4
|
+
const env = envOverride || process.env.NODE_ENV || 'dev';
|
|
5
|
+
|
|
6
|
+
let envConfig;
|
|
7
|
+
try {
|
|
8
|
+
envConfig = require(`./environment.${env}.js`);
|
|
9
|
+
} catch (error) {
|
|
10
|
+
logInfo('pegasus-sdk', `Warning: Could not load config for environment "${env}", falling back to development`);
|
|
11
|
+
envConfig = require('./environment.dev.js');
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const config = { ...envConfig };
|
|
15
|
+
|
|
16
|
+
if (process.env.PEGASUS_SDK_DB_SECRET_ARN) {
|
|
17
|
+
config.secretName = process.env.PEGASUS_SDK_DB_SECRET_ARN;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (process.env.PEGASUS_SDK_OPENSEARCH_ENDPOINT) {
|
|
21
|
+
config.openSearchEndpoint = process.env.PEGASUS_SDK_OPENSEARCH_ENDPOINT;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (process.env.PEGASUS_SDK_OPENSEARCH_INDEX) {
|
|
25
|
+
config.openSearchIndex = process.env.PEGASUS_SDK_OPENSEARCH_INDEX;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (process.env.PEGASUS_SDK_DATABASE_HOST) {
|
|
29
|
+
config.database.host = process.env.PEGASUS_SDK_DATABASE_HOST;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (process.env.PEGASUS_SDK_DATABASE_NAME) {
|
|
33
|
+
config.database.name = process.env.PEGASUS_SDK_DATABASE_NAME;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (process.env.PEGASUS_SDK_AWS_REGION) {
|
|
37
|
+
config.region = process.env.PEGASUS_SDK_AWS_REGION;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (process.env.PEGASUS_SDK_MAX_CONNECTIONS) {
|
|
41
|
+
config.postgres.maxConnections = parseInt(process.env.PEGASUS_SDK_MAX_CONNECTIONS, 10);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (process.env.PEGASUS_SDK_MIN_CONNECTIONS) {
|
|
45
|
+
config.postgres.minConnections = parseInt(process.env.PEGASUS_SDK_MIN_CONNECTIONS, 10);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (process.env.PEGASUS_SDK_IDLE_TIMEOUT) {
|
|
49
|
+
config.postgres.idleTimeoutMillis = parseInt(process.env.PEGASUS_SDK_IDLE_TIMEOUT, 10);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (process.env.PEGASUS_SDK_CONNECTION_TIMEOUT) {
|
|
53
|
+
config.postgres.connectionTimeoutMillis = parseInt(process.env.PEGASUS_SDK_CONNECTION_TIMEOUT, 10);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (process.env.PEGASUS_SDK_STATEMENT_TIMEOUT) {
|
|
57
|
+
config.postgres.statementTimeout = parseInt(process.env.PEGASUS_SDK_STATEMENT_TIMEOUT, 10);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (process.env.PEGASUS_SDK_QUERY_TIMEOUT) {
|
|
61
|
+
config.postgres.queryTimeout = parseInt(process.env.PEGASUS_SDK_QUERY_TIMEOUT, 10);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { loadConfig };
|
package/index.js
CHANGED
|
@@ -4,15 +4,21 @@ const DocumentsService = require('./lib/documents');
|
|
|
4
4
|
const SearchService = require('./lib/search');
|
|
5
5
|
const SyncService = require('./lib/sync');
|
|
6
6
|
const UtilsService = require('./lib/utils');
|
|
7
|
+
const ElasticsearchService = require('./lib/elasticsearch');
|
|
7
8
|
|
|
8
9
|
class PegasusSDK {
|
|
9
10
|
constructor(config) {
|
|
10
11
|
this.connection = new PegasusConnection(config);
|
|
12
|
+
this.elasticsearch = new ElasticsearchService(this.connection);
|
|
11
13
|
this.chemicals = new ChemicalsService(this.connection);
|
|
12
14
|
this.documents = new DocumentsService(this.connection);
|
|
13
15
|
this.search = new SearchService(this.connection);
|
|
14
16
|
this.sync = new SyncService(this.connection);
|
|
15
17
|
this.utils = new UtilsService(this.connection);
|
|
18
|
+
|
|
19
|
+
this.chemicals.registerElasticsearchHandlers(this.elasticsearch);
|
|
20
|
+
this.documents.registerElasticsearchHandlers(this.elasticsearch);
|
|
21
|
+
this.search.registerElasticsearchHandlers(this.elasticsearch);
|
|
16
22
|
}
|
|
17
23
|
|
|
18
24
|
async connect() {
|
|
@@ -35,3 +41,4 @@ module.exports.DocumentsService = DocumentsService;
|
|
|
35
41
|
module.exports.SearchService = SearchService;
|
|
36
42
|
module.exports.SyncService = SyncService;
|
|
37
43
|
module.exports.UtilsService = UtilsService;
|
|
44
|
+
module.exports.ElasticsearchService = ElasticsearchService;
|
package/lib/chemicals.js
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
|
+
const { logError } = require('@toxplanet/tphelper/logging');
|
|
2
|
+
const { getDrizzle, schema } = require('./db');
|
|
3
|
+
const { eq } = require('drizzle-orm');
|
|
4
|
+
|
|
1
5
|
class ChemicalsService {
|
|
2
|
-
constructor(connection) {
|
|
6
|
+
constructor(connection) {
|
|
7
|
+
this.connection = connection;
|
|
8
|
+
this.db = null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
getDb() {
|
|
12
|
+
if (!this.db) {
|
|
13
|
+
this.db = getDrizzle(this.connection.pgPool);
|
|
14
|
+
}
|
|
15
|
+
return this.db;
|
|
16
|
+
}
|
|
3
17
|
|
|
4
18
|
async bulkIndexFielded(documents) {}
|
|
5
19
|
|
|
@@ -7,11 +21,50 @@ class ChemicalsService {
|
|
|
7
21
|
|
|
8
22
|
async bulkIndexSubstances(substances) {}
|
|
9
23
|
|
|
10
|
-
async createChemical(chemical) {
|
|
24
|
+
async createChemical(chemical) {
|
|
25
|
+
try {
|
|
26
|
+
const db = this.getDb();
|
|
27
|
+
|
|
28
|
+
const [result] = await db
|
|
29
|
+
.insert(schema.chemicals)
|
|
30
|
+
.values({
|
|
31
|
+
sourceId: chemical.source_id,
|
|
32
|
+
chemicalName: chemical.chemical_name,
|
|
33
|
+
chemicalMeta: chemical.chemical_meta,
|
|
34
|
+
chemicalIdentifiers: chemical.chemical_identifiers,
|
|
35
|
+
chemicalSynonyms: chemical.chemical_synonyms,
|
|
36
|
+
chemicalCategories: chemical.chemical_categories,
|
|
37
|
+
createdAt: chemical.created_at || new Date(),
|
|
38
|
+
updatedAt: chemical.updated_at || new Date(),
|
|
39
|
+
...(chemical.imported_at && { importedAt: chemical.imported_at }),
|
|
40
|
+
...(chemical.chemical_id && { chemicalId: chemical.chemical_id })
|
|
41
|
+
})
|
|
42
|
+
.returning();
|
|
43
|
+
|
|
44
|
+
return result;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
logError('pegasus-sdk', 'ChemicalsService', 'createChemical', error);
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
11
50
|
|
|
12
51
|
async updateChemical(chemicalId, updates) {}
|
|
13
52
|
|
|
14
|
-
async deleteChemical(chemicalId) {
|
|
53
|
+
async deleteChemical(chemicalId) {
|
|
54
|
+
try {
|
|
55
|
+
const db = this.getDb();
|
|
56
|
+
|
|
57
|
+
const [deleted] = await db
|
|
58
|
+
.delete(schema.chemicals)
|
|
59
|
+
.where(eq(schema.chemicals.chemicalId, chemicalId))
|
|
60
|
+
.returning();
|
|
61
|
+
|
|
62
|
+
return deleted || null;
|
|
63
|
+
} catch (error) {
|
|
64
|
+
logError('pegasus-sdk', 'ChemicalsService', 'deleteChemical', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
15
68
|
|
|
16
69
|
async deleteBySourceId(sourceId) {}
|
|
17
70
|
|
|
@@ -21,7 +74,22 @@ class ChemicalsService {
|
|
|
21
74
|
|
|
22
75
|
async bulkUpdateProperty(filter, propertyPath, newValue) {}
|
|
23
76
|
|
|
24
|
-
async getChemicalById(chemicalId) {
|
|
77
|
+
async getChemicalById(chemicalId) {
|
|
78
|
+
try {
|
|
79
|
+
const db = this.getDb();
|
|
80
|
+
|
|
81
|
+
const [result] = await db
|
|
82
|
+
.select()
|
|
83
|
+
.from(schema.chemicals)
|
|
84
|
+
.where(eq(schema.chemicals.chemicalId, chemicalId))
|
|
85
|
+
.limit(1);
|
|
86
|
+
|
|
87
|
+
return result || null;
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logError('pegasus-sdk', 'ChemicalsService', 'getChemicalById', error);
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
25
93
|
|
|
26
94
|
async getChemicalBySourceId(sourceId) {}
|
|
27
95
|
|
|
@@ -29,8 +97,6 @@ class ChemicalsService {
|
|
|
29
97
|
|
|
30
98
|
async getChemicalsByIdentifier(identifierType, identifierValue) {}
|
|
31
99
|
|
|
32
|
-
async countAll() {}
|
|
33
|
-
|
|
34
100
|
async countByCollection(collectionName) {}
|
|
35
101
|
|
|
36
102
|
async countByIdentifier(identifierValue) {}
|
|
@@ -45,13 +111,183 @@ class ChemicalsService {
|
|
|
45
111
|
|
|
46
112
|
async convertIdentifiersBatch(fromIdentifiers, toIdentifierType) {}
|
|
47
113
|
|
|
48
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Search for chemicals by name using OpenSearch
|
|
116
|
+
* @param {string} searchTerm - Name to search for
|
|
117
|
+
* @param {number} limit - Maximum number of results (default: 10)
|
|
118
|
+
* @returns {Promise<Object>} Search results
|
|
119
|
+
*/
|
|
120
|
+
async searchByName(searchTerm, limit = 10) {
|
|
121
|
+
if (!searchTerm) {
|
|
122
|
+
return { results: [] };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const opensearchClient = this.connection.getOpenSearchClient();
|
|
127
|
+
const indexName = this.connection.getOpenSearchIndex();
|
|
128
|
+
|
|
129
|
+
const response = await opensearchClient.search({
|
|
130
|
+
index: indexName,
|
|
131
|
+
body: {
|
|
132
|
+
size: limit,
|
|
133
|
+
query: {
|
|
134
|
+
bool: {
|
|
135
|
+
should: [
|
|
136
|
+
// Prioritize exact name matches
|
|
137
|
+
{ term: { 'chemical_name.keyword': { value: searchTerm, boost: 100, case_insensitive: true } } },
|
|
138
|
+
// Then prefix matches
|
|
139
|
+
{ prefix: { 'chemical_name.keyword': { value: searchTerm, boost: 50, case_insensitive: true } } },
|
|
140
|
+
// Include synonym matches as secondary
|
|
141
|
+
{ term: { 'synonyms.keyword': { value: searchTerm, boost: 30, case_insensitive: true } } },
|
|
142
|
+
{ prefix: { 'synonyms.keyword': { value: searchTerm, boost: 10, case_insensitive: true } } }
|
|
143
|
+
],
|
|
144
|
+
minimum_should_match: 1
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
_source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
|
|
148
|
+
}
|
|
149
|
+
});
|
|
49
150
|
|
|
50
|
-
|
|
151
|
+
const hits = response.body?.hits?.hits || [];
|
|
152
|
+
const results = hits.map((hit) => ({
|
|
153
|
+
id: hit._source.postgres_id,
|
|
154
|
+
name: hit._source.chemical_name,
|
|
155
|
+
cas: hit._source.cas_numbers || [],
|
|
156
|
+
identifiers: hit._source.identifier_values || [],
|
|
157
|
+
synonyms: hit._source.synonyms || [],
|
|
158
|
+
score: hit._score
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
return { results };
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logError('pegasus-sdk', 'ChemicalsService', 'searchByName', error);
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Search for chemicals by synonym using OpenSearch
|
|
170
|
+
* @param {string} synonymTerm - Synonym to search for
|
|
171
|
+
* @param {number} limit - Maximum number of results (default: 10)
|
|
172
|
+
* @returns {Promise<Object>} Search results
|
|
173
|
+
*/
|
|
174
|
+
async searchBySynonym(synonymTerm, limit = 10) {
|
|
175
|
+
if (!synonymTerm) {
|
|
176
|
+
return { results: [] };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const opensearchClient = this.connection.getOpenSearchClient();
|
|
181
|
+
const indexName = this.connection.getOpenSearchIndex();
|
|
182
|
+
|
|
183
|
+
const response = await opensearchClient.search({
|
|
184
|
+
index: indexName,
|
|
185
|
+
body: {
|
|
186
|
+
size: limit,
|
|
187
|
+
query: {
|
|
188
|
+
bool: {
|
|
189
|
+
should: [
|
|
190
|
+
// Prioritize exact synonym matches
|
|
191
|
+
{ term: { 'synonyms.keyword': { value: synonymTerm, boost: 100, case_insensitive: true } } },
|
|
192
|
+
// Then prefix matches
|
|
193
|
+
{ prefix: { 'synonyms.keyword': { value: synonymTerm, boost: 50, case_insensitive: true } } },
|
|
194
|
+
// Include name matches as secondary
|
|
195
|
+
{ term: { 'chemical_name.keyword': { value: synonymTerm, boost: 30, case_insensitive: true } } },
|
|
196
|
+
{ prefix: { 'chemical_name.keyword': { value: synonymTerm, boost: 10, case_insensitive: true } } }
|
|
197
|
+
],
|
|
198
|
+
minimum_should_match: 1
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
_source: ['postgres_id', 'chemical_name', 'cas_numbers', 'identifier_values', 'synonyms']
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
const hits = response.body?.hits?.hits || [];
|
|
206
|
+
const results = hits.map((hit) => ({
|
|
207
|
+
id: hit._source.postgres_id,
|
|
208
|
+
name: hit._source.chemical_name,
|
|
209
|
+
cas: hit._source.cas_numbers || [],
|
|
210
|
+
identifiers: hit._source.identifier_values || [],
|
|
211
|
+
synonyms: hit._source.synonyms || [],
|
|
212
|
+
score: hit._score
|
|
213
|
+
}));
|
|
214
|
+
|
|
215
|
+
return { results };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
logError('pegasus-sdk', 'ChemicalsService', 'searchBySynonym', error);
|
|
218
|
+
throw error;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async countAll() {
|
|
223
|
+
try {
|
|
224
|
+
const db = this.getDb();
|
|
225
|
+
const result = await db.select({ count: sql`count(*)::int` }).from(schema.chemicals);
|
|
226
|
+
return { count: result[0].count };
|
|
227
|
+
} catch (error) {
|
|
228
|
+
logError('pegasus-sdk', 'ChemicalsService', 'countAll', error);
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
51
232
|
|
|
52
233
|
async findChemicalsWithoutDocuments(collectionName, searchTerm, pageSize) {}
|
|
53
234
|
|
|
54
235
|
async countChemicalsWithoutDocuments(collectionName) {}
|
|
236
|
+
|
|
237
|
+
registerElasticsearchHandlers(elasticsearchService) {
|
|
238
|
+
const indexPatterns = this.connection.config.indexRoutes?.chemicals || ['chemicals*'];
|
|
239
|
+
|
|
240
|
+
indexPatterns.forEach(pattern => {
|
|
241
|
+
elasticsearchService.registerIndexRoute(pattern, {
|
|
242
|
+
index: async (params) => {
|
|
243
|
+
const chemical = params.body;
|
|
244
|
+
return await this.createChemical(chemical);
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
bulk: async (params) => {
|
|
248
|
+
const operations = params.body || params.operations;
|
|
249
|
+
const documents = [];
|
|
250
|
+
|
|
251
|
+
for (let i = 0; i < operations.length; i += 2) {
|
|
252
|
+
const action = operations[i];
|
|
253
|
+
const document = operations[i + 1];
|
|
254
|
+
|
|
255
|
+
if (action.index || action.create) {
|
|
256
|
+
documents.push(document);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return await this.bulkIndexFielded(documents);
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
get: async (params) => {
|
|
264
|
+
return await this.getChemicalById(params.id);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
update: async (params) => {
|
|
268
|
+
return await this.updateChemical(params.id, params.body);
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
delete: async (params) => {
|
|
272
|
+
return await this.deleteChemical(params.id);
|
|
273
|
+
},
|
|
274
|
+
|
|
275
|
+
search: async (params) => {
|
|
276
|
+
const query = params.body?.query;
|
|
277
|
+
const searchTerm = query?.match?.chemical_name ||
|
|
278
|
+
query?.term?.chemical_name ||
|
|
279
|
+
query?.query_string?.query || '';
|
|
280
|
+
const limit = params.body?.size || 10;
|
|
281
|
+
|
|
282
|
+
return await this.searchByName(searchTerm, limit);
|
|
283
|
+
},
|
|
284
|
+
|
|
285
|
+
count: async (params) => {
|
|
286
|
+
return await this.countAll();
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
55
291
|
}
|
|
56
292
|
|
|
57
293
|
module.exports = ChemicalsService;
|
package/lib/connection.js
CHANGED
|
@@ -2,15 +2,22 @@ const { Pool } = require('pg');
|
|
|
2
2
|
const { Client } = require('@opensearch-project/opensearch');
|
|
3
3
|
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
|
|
4
4
|
const { AwsSigv4Signer } = require('@opensearch-project/opensearch/aws');
|
|
5
|
-
const {
|
|
5
|
+
const { fromNodeProviderChain } = require('@aws-sdk/credential-providers');
|
|
6
|
+
const { loadConfig } = require('../config');
|
|
7
|
+
const { logInfo, logError } = require('@toxplanet/tphelper/logging');
|
|
6
8
|
|
|
7
9
|
class PegasusConnection {
|
|
8
10
|
constructor(config = {}) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
13
|
-
this.
|
|
11
|
+
const envConfig = loadConfig(config.environment);
|
|
12
|
+
|
|
13
|
+
this.config = { ...envConfig, ...config };
|
|
14
|
+
this.environment = this.config.environment;
|
|
15
|
+
this.region = this.config.region;
|
|
16
|
+
this.secretName = this.config.secretName;
|
|
17
|
+
this.openSearchEndpoint = this.config.openSearchEndpoint;
|
|
18
|
+
this.openSearchIndex = this.config.openSearchIndex;
|
|
19
|
+
this.databaseHost = this.config.database?.host;
|
|
20
|
+
this.databaseName = this.config.database?.name;
|
|
14
21
|
|
|
15
22
|
this.pgPool = null;
|
|
16
23
|
this.osClient = null;
|
|
@@ -47,35 +54,33 @@ class PegasusConnection {
|
|
|
47
54
|
const secret = await this.getSecret();
|
|
48
55
|
|
|
49
56
|
const poolConfig = {
|
|
50
|
-
host:
|
|
51
|
-
port:
|
|
52
|
-
database:
|
|
53
|
-
user: secret.username
|
|
57
|
+
host: this.databaseHost,
|
|
58
|
+
port: 5432,
|
|
59
|
+
database: this.databaseName,
|
|
60
|
+
user: secret.username,
|
|
54
61
|
password: secret.password,
|
|
55
|
-
max: this.config.maxConnections
|
|
56
|
-
min: this.config.minConnections
|
|
57
|
-
idleTimeoutMillis: this.config.idleTimeoutMillis
|
|
58
|
-
connectionTimeoutMillis: this.config.connectionTimeoutMillis
|
|
62
|
+
max: this.config.postgres.maxConnections,
|
|
63
|
+
min: this.config.postgres.minConnections,
|
|
64
|
+
idleTimeoutMillis: this.config.postgres.idleTimeoutMillis,
|
|
65
|
+
connectionTimeoutMillis: this.config.postgres.connectionTimeoutMillis,
|
|
59
66
|
allowExitOnIdle: true,
|
|
60
|
-
ssl:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
statement_timeout: this.config.statementTimeout || 30000,
|
|
64
|
-
query_timeout: this.config.queryTimeout || 30000
|
|
67
|
+
ssl: this.config.postgres.ssl,
|
|
68
|
+
statement_timeout: this.config.postgres.statementTimeout,
|
|
69
|
+
query_timeout: this.config.postgres.queryTimeout
|
|
65
70
|
};
|
|
66
71
|
|
|
67
72
|
this.pgPool = new Pool(poolConfig);
|
|
68
73
|
|
|
69
74
|
this.pgPool.on('error', (err) => {
|
|
70
|
-
|
|
75
|
+
logError('pegasus-sdk', 'PegasusConnection', 'pgPool.error', err);
|
|
71
76
|
});
|
|
72
77
|
|
|
73
78
|
this.pgPool.on('connect', () => {
|
|
74
|
-
|
|
79
|
+
logInfo('pegasus-sdk', 'PostgreSQL client connected');
|
|
75
80
|
});
|
|
76
81
|
|
|
77
82
|
this.pgPool.on('remove', () => {
|
|
78
|
-
|
|
83
|
+
logInfo('pegasus-sdk', 'PostgreSQL client removed from pool');
|
|
79
84
|
});
|
|
80
85
|
|
|
81
86
|
if (this.openSearchEndpoint) {
|
|
@@ -84,7 +89,7 @@ class PegasusConnection {
|
|
|
84
89
|
region: this.region,
|
|
85
90
|
service: 'aoss',
|
|
86
91
|
getCredentials: () => {
|
|
87
|
-
const credentialsProvider =
|
|
92
|
+
const credentialsProvider = fromNodeProviderChain();
|
|
88
93
|
return credentialsProvider();
|
|
89
94
|
}
|
|
90
95
|
}),
|
|
@@ -124,6 +129,10 @@ class PegasusConnection {
|
|
|
124
129
|
return this.osClient;
|
|
125
130
|
}
|
|
126
131
|
|
|
132
|
+
getOpenSearchIndex() {
|
|
133
|
+
return this.openSearchIndex || 'chemicals';
|
|
134
|
+
}
|
|
135
|
+
|
|
127
136
|
async testConnection() {
|
|
128
137
|
try {
|
|
129
138
|
if (this.pgPool) {
|
|
@@ -143,11 +152,19 @@ class PegasusConnection {
|
|
|
143
152
|
let osStatus = null;
|
|
144
153
|
if (this.osClient) {
|
|
145
154
|
try {
|
|
146
|
-
const
|
|
155
|
+
const indexName = this.getOpenSearchIndex();
|
|
156
|
+
const testSearch = await this.osClient.search({
|
|
157
|
+
index: indexName,
|
|
158
|
+
body: {
|
|
159
|
+
size: 1,
|
|
160
|
+
query: {
|
|
161
|
+
match: { chemical_name: 'benzene' }
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
147
165
|
osStatus = {
|
|
148
166
|
connected: true,
|
|
149
|
-
|
|
150
|
-
cluster: osInfo.body.cluster_name
|
|
167
|
+
resultsFound: testSearch.body.hits.total.value || 0
|
|
151
168
|
};
|
|
152
169
|
} catch (osError) {
|
|
153
170
|
osStatus = {
|
package/lib/db/index.js
ADDED
package/lib/db/schema.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const { pgTable, uuid, text, jsonb, timestamp, index, uniqueIndex } = require('drizzle-orm/pg-core');
|
|
2
|
+
const { sql } = require('drizzle-orm');
|
|
3
|
+
|
|
4
|
+
const chemicals = pgTable('chemicals', {
|
|
5
|
+
chemicalId: uuid('chemical_id').defaultRandom().primaryKey(),
|
|
6
|
+
sourceId: text('source_id').notNull().unique(),
|
|
7
|
+
chemicalName: text('chemical_name').notNull(),
|
|
8
|
+
chemicalMeta: jsonb('chemical_meta'),
|
|
9
|
+
chemicalIdentifiers: jsonb('chemical_identifiers'),
|
|
10
|
+
chemicalSynonyms: text('chemical_synonyms').array(),
|
|
11
|
+
chemicalCategories: text('chemical_categories').array(),
|
|
12
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull(),
|
|
13
|
+
updatedAt: timestamp('updated_at', { withTimezone: true }).notNull(),
|
|
14
|
+
importedAt: timestamp('imported_at', { withTimezone: true }).defaultNow()
|
|
15
|
+
}, (table) => {
|
|
16
|
+
return {
|
|
17
|
+
sourceIdIdx: uniqueIndex('idx_chemicals_source_id').on(table.sourceId),
|
|
18
|
+
nameIdx: index('idx_chemicals_name').on(table.chemicalName),
|
|
19
|
+
createdAtIdx: index('idx_chemicals_created_at').on(table.createdAt),
|
|
20
|
+
updatedAtIdx: index('idx_chemicals_updated_at').on(table.updatedAt),
|
|
21
|
+
identifiersGinIdx: index('idx_chemicals_identifiers_gin').on(table.chemicalIdentifiers),
|
|
22
|
+
synonymsGinIdx: index('idx_chemicals_synonyms_gin').on(table.chemicalSynonyms)
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
module.exports = {
|
|
27
|
+
chemicals
|
|
28
|
+
};
|