Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit afe9bed

Browse files
Merge pull request #25 from topcoder-platform/develop
Sync master with develop
2 parents 2f14cc7 + 562ed64 commit afe9bed

File tree

12 files changed

+260
-185
lines changed

12 files changed

+260
-185
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ The following parameters can be set in config files or in env variables:
6464
- ELASTICCLOUD_ID: The elastic cloud id, if your elasticsearch instance is hosted on elastic cloud. DO NOT provide a value for ES_HOST if you are using this
6565
- ELASTICCLOUD_USERNAME: The elastic cloud username for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud
6666
- ELASTICCLOUD_PASSWORD: The elastic cloud password for basic authentication. Provide this only if your elasticsearch instance is hosted on elastic cloud
67+
- AUTH0_URL: The auth0 url, Default is 'https://topcoder-dev.auth0.com/oauth/token'
68+
- AUTH0_AUDIENCE: The auth0 audience for accessing ubahn api(s), Default is 'https://m2m.topcoder-dev.com/'
69+
- AUTH0_CLIENT_ID: The auth0 client id
70+
- AUTH0_CLIENT_SECRET: The auth0 client secret
71+
- AUTH0_PROXY_SERVER_URL: The auth0 proxy server url
72+
- TOKEN_CACHE_TIME: The token cache time
73+
- TOPCODER_GROUP_API: The topcoder groups api, Default is 'https://api.topcoder-dev.com/v5/groups'
6774

6875
There is a `/health` endpoint that checks for the health of the app. This sets up an expressjs server and listens on the environment variable `PORT`. It's not part of the configuration file and needs to be passed as an environment variable
6976

@@ -89,7 +96,7 @@ Configuration for the tests is at `config/test.js`, only add such new configurat
8996
docker-compose up -d
9097
```
9198

92-
3. initialize Elasticsearch, create configured Elasticsearch index: `npm run init-es force`
99+
3. initialize Elasticsearch. Execute the `insert-data` script in the [API repository](https://github.com/topcoder-platform/u-bahn-api) to set it up and then clear only the data
93100

94101
## Local deployment
95102

config/default.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ module.exports = {
1414
// Kafka group id
1515
KAFKA_GROUP_ID: process.env.KAFKA_GROUP_ID || 'ubahn-processor-es',
1616

17+
TOPCODER_GROUP_API: process.env.TOPCODER_GROUP_API || 'https://api.topcoder-dev.com/v5/groups',
18+
19+
AUTH0_URL: process.env.AUTH0_URL || 'https://topcoder-dev.auth0.com/oauth/token', // Auth0 credentials
20+
AUTH0_AUDIENCE: process.env.AUTH0_AUDIENCE || 'https://m2m.topcoder-dev.com/',
21+
TOKEN_CACHE_TIME: process.env.TOKEN_CACHE_TIME,
22+
AUTH0_CLIENT_ID: process.env.AUTH0_CLIENT_ID,
23+
AUTH0_CLIENT_SECRET: process.env.AUTH0_CLIENT_SECRET,
24+
AUTH0_PROXY_SERVER_URL: process.env.AUTH0_PROXY_SERVER_URL,
25+
1726
UBAHN_CREATE_TOPIC: process.env.UBAHN_CREATE_TOPIC || 'u-bahn.action.create',
1827
UBAHN_UPDATE_TOPIC: process.env.UBAHN_UPDATE_TOPIC || 'u-bahn.action.update',
1928
UBAHN_DELETE_TOPIC: process.env.UBAHN_DELETE_TOPIC || 'u-bahn.action.delete',
@@ -84,6 +93,26 @@ module.exports = {
8493
},
8594
organization: {
8695
enrichPolicyName: process.env.ORGANIZATION_ENRICH_POLICYNAME || 'organization-policy'
96+
},
97+
// sub resources under user
98+
achievement: {
99+
userField: process.env.USER_ACHIEVEMENT_PROPERTY_NAME || 'achievements'
100+
},
101+
externalprofile: {
102+
userField: process.env.USER_EXTERNALPROFILE_PROPERTY_NAME || 'externalProfiles'
103+
},
104+
userattribute: {
105+
userField: process.env.USER_ATTRIBUTE_PROPERTY_NAME || 'attributes'
106+
},
107+
userrole: {
108+
userField: process.env.USER_ROLE_PROPERTY_NAME || 'roles'
109+
},
110+
userskill: {
111+
userField: process.env.USER_SKILL_PROPERTY_NAME || 'skills'
112+
},
113+
// sub resources under organization
114+
organizationskillprovider: {
115+
orgField: process.env.ORGANIZATION_SKILLPROVIDER_PROPERTY_NAME || 'skillProviders'
87116
}
88117
}
89118
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"start": "node src/app.js",
88
"lint": "standard",
99
"lint:fix": "standard --fix",
10-
"init-es": "node test/common/init-es.js",
1110
"view-data": "node test/common/view-data.js",
1211
"test": "mocha test/unit/test.js --require test/unit/prepare.js --timeout 20000 --exit",
1312
"test:cov": "nyc --reporter=html --reporter=text mocha test/unit/test.js --require test/unit/prepare.js --timeout 20000 --exit",
@@ -35,6 +34,8 @@
3534
"get-parameter-names": "^0.3.0",
3635
"lodash": "^4.17.19",
3736
"no-kafka": "^3.4.3",
37+
"axios": "^0.19.2",
38+
"tc-core-library-js": "appirio-tech/tc-core-library-js.git#v2.6",
3839
"topcoder-healthcheck-dropin": "^1.0.3",
3940
"winston": "^3.2.1"
4041
},

src/common/constants.js

Lines changed: 69 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,18 @@ const topResources = {
1010
index: config.get('ES.ACHIEVEMENT_PROVIDER_INDEX'),
1111
type: config.get('ES.ACHIEVEMENT_PROVIDER_TYPE'),
1212
enrich: {
13-
policyName: config.get('ES.ENRICHMENT.achievementprovider.enrichPolicyName')
13+
policyName: config.get('ES.ENRICHMENT.achievementprovider.enrichPolicyName'),
14+
matchField: 'id',
15+
enrichFields: ['id', 'name', 'created', 'updated', 'createdBy', 'updatedBy']
1416
}
1517
},
1618
attribute: {
1719
index: config.get('ES.ATTRIBUTE_INDEX'),
1820
type: config.get('ES.ATTRIBUTE_TYPE'),
1921
enrich: {
20-
policyName: config.get('ES.ENRICHMENT.attribute.enrichPolicyName')
22+
policyName: config.get('ES.ENRICHMENT.attribute.enrichPolicyName'),
23+
matchField: 'id',
24+
enrichFields: ['id', 'name', 'attributeGroupId', 'created', 'updated', 'createdBy', 'updatedBy', 'attributegroup']
2125
},
2226
ingest: {
2327
pipeline: {
@@ -29,7 +33,15 @@ const topResources = {
2933
index: config.get('ES.ATTRIBUTE_GROUP_INDEX'),
3034
type: config.get('ES.ATTRIBUTE_GROUP_TYPE'),
3135
enrich: {
32-
policyName: config.get('ES.ENRICHMENT.attributegroup.enrichPolicyName')
36+
policyName: config.get('ES.ENRICHMENT.attributegroup.enrichPolicyName'),
37+
matchField: 'id',
38+
enrichFields: ['id', 'name', 'organizationId', 'created', 'updated', 'createdBy', 'updatedBy']
39+
},
40+
pipeline: {
41+
id: config.get('ES.ENRICHMENT.attributegroup.pipelineId'),
42+
field: 'attributeGroupId',
43+
targetField: 'attributegroup',
44+
maxMatches: '1'
3345
}
3446
},
3547
organization: {
@@ -40,14 +52,18 @@ const topResources = {
4052
index: config.get('ES.ROLE_INDEX'),
4153
type: config.get('ES.ROLE_TYPE'),
4254
enrich: {
43-
policyName: config.get('ES.ENRICHMENT.role.enrichPolicyName')
55+
policyName: config.get('ES.ENRICHMENT.role.enrichPolicyName'),
56+
matchField: 'id',
57+
enrichFields: ['id', 'name', 'created', 'updated', 'createdBy', 'updatedBy']
4458
}
4559
},
4660
skill: {
4761
index: config.get('ES.SKILL_INDEX'),
4862
type: config.get('ES.SKILL_TYPE'),
4963
enrich: {
50-
policyName: config.get('ES.ENRICHMENT.skill.enrichPolicyName')
64+
policyName: config.get('ES.ENRICHMENT.skill.enrichPolicyName'),
65+
matchField: 'id',
66+
enrichFields: ['id', 'skillProviderId', 'name', 'externalId', 'uri', 'created', 'updated', 'createdBy', 'updatedBy', 'skillprovider']
5167
},
5268
ingest: {
5369
pipeline: {
@@ -59,7 +75,15 @@ const topResources = {
5975
index: config.get('ES.SKILL_PROVIDER_INDEX'),
6076
type: config.get('ES.SKILL_PROVIDER_TYPE'),
6177
enrich: {
62-
policyName: config.get('ES.ENRICHMENT.skillprovider.enrichPolicyName')
78+
policyName: config.get('ES.ENRICHMENT.skillprovider.enrichPolicyName'),
79+
matchField: 'id',
80+
enrichFields: ['id', 'name', 'created', 'updated', 'createdBy', 'updatedBy']
81+
},
82+
pipeline: {
83+
id: config.get('ES.ENRICHMENT.skillprovider.pipelineId'),
84+
field: 'skillProviderId',
85+
targetField: 'skillprovider',
86+
maxMatches: '1'
6387
}
6488
},
6589
user: {
@@ -69,6 +93,39 @@ const topResources = {
6993
pipeline: {
7094
id: config.get('ES.ENRICHMENT.user.pipelineId')
7195
}
96+
},
97+
pipeline: {
98+
id: config.get('ES.ENRICHMENT.user.pipelineId'),
99+
processors: [
100+
{
101+
referenceField: config.get('ES.ENRICHMENT.achievement.userField'),
102+
enrichPolicyName: config.get('ES.ENRICHMENT.achievementprovider.enrichPolicyName'),
103+
field: '_ingest._value.achievementsProviderId',
104+
targetField: '_ingest._value.achievementprovider',
105+
maxMatches: '1'
106+
},
107+
{
108+
referenceField: config.get('ES.ENRICHMENT.userattribute.userField'),
109+
enrichPolicyName: config.get('ES.ENRICHMENT.attribute.enrichPolicyName'),
110+
field: '_ingest._value.attributeId',
111+
targetField: '_ingest._value.attribute',
112+
maxMatches: '1'
113+
},
114+
{
115+
referenceField: config.get('ES.ENRICHMENT.userrole.userField'),
116+
enrichPolicyName: config.get('ES.ENRICHMENT.role.enrichPolicyName'),
117+
field: '_ingest._value.roleId',
118+
targetField: '_ingest._value.role',
119+
maxMatches: '1'
120+
},
121+
{
122+
referenceField: config.get('ES.ENRICHMENT.userskill.userField'),
123+
enrichPolicyName: config.get('ES.ENRICHMENT.skill.enrichPolicyName'),
124+
field: '_ingest._value.skillId',
125+
targetField: '_ingest._value.skill',
126+
maxMatches: '1'
127+
}
128+
]
72129
}
73130
}
74131
}
@@ -106,7 +163,12 @@ const organizationResources = {
106163
organizationskillprovider: {
107164
propertyName: config.get('ES.ORGANIZATION_SKILLPROVIDER_PROPERTY_NAME'),
108165
relateKey: 'skillProviderId',
109-
validate: payload => validProperties(payload, ['organizationId', 'skillProviderId'])
166+
validate: payload => validProperties(payload, ['organizationId', 'skillProviderId']),
167+
enrich: {
168+
policyName: config.get('ES.ENRICHMENT.organization.enrichPolicyName'),
169+
matchField: 'id',
170+
enrichFields: ['id', 'name', 'created', 'updated', 'createdBy', 'updatedBy', 'skillProviders']
171+
}
110172
}
111173
}
112174

src/common/helper.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ const elasticsearch = require('@elastic/elasticsearch')
88
const _ = require('lodash')
99
const Joi = require('@hapi/joi')
1010
const { Mutex } = require('async-mutex')
11+
const axios = require('axios')
12+
const logger = require('./logger')
13+
const m2mAuth = require('tc-core-library-js').auth.m2m
14+
const topcoderM2M = m2mAuth(_.pick(config, ['AUTH0_URL', 'AUTH0_AUDIENCE', 'TOKEN_CACHE_TIME', 'AUTH0_PROXY_SERVER_URL']))
1115

1216
AWS.config.region = config.ES.AWS_REGION
1317

@@ -18,6 +22,34 @@ let transactionId
1822
const esClientMutex = new Mutex()
1923
const mutexReleaseMap = {}
2024

25+
/* Function to get M2M token
26+
* (Topcoder APIs only)
27+
* @returns {Promise}
28+
*/
29+
async function getTopcoderM2Mtoken () {
30+
return topcoderM2M.getMachineToken(config.AUTH0_CLIENT_ID, config.AUTH0_CLIENT_SECRET)
31+
}
32+
33+
/**
34+
* Returns the user in Topcoder identified by the email
35+
* @param {String} email The user email
36+
*/
37+
async function getUserGroup (memberId) {
38+
const url = config.TOPCODER_GROUP_API
39+
const token = await getTopcoderM2Mtoken()
40+
const params = { memberId, membershipType: 'user', page: 1 }
41+
42+
logger.debug(`request GET ${url} with params: ${JSON.stringify(params)}`)
43+
let groups = []
44+
let groupRes = await axios.get(url, { headers: { Authorization: `Bearer ${token}` }, params })
45+
while (groupRes.data.length > 0) {
46+
groups = _.concat(groups, _.map(groupRes.data, g => _.pick(g, 'id', 'name')))
47+
params.page = params.page + 1
48+
groupRes = await axios.get(url, { headers: { Authorization: `Bearer ${token}` }, params })
49+
}
50+
return groups
51+
}
52+
2153
/**
2254
* Get Kafka options
2355
* @return {Object} the Kafka options
@@ -201,6 +233,7 @@ module.exports = {
201233
getESClient,
202234
validProperties,
203235
getUser,
236+
getUserGroup,
204237
updateUser,
205238
getOrg,
206239
updateOrg,

src/services/ProcessorService.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@ async function processCreate (message, transactionId) {
4949
user[userResource.propertyName] = []
5050
}
5151

52+
// import groups for a new user
53+
if (resource === 'externalprofile' && message.payload.externalId) {
54+
const userGroups = await helper.getUserGroup(message.payload.externalId)
55+
user[config.get('ES.USER_GROUP_PROPERTY_NAME')] = _.unionBy(user[config.get('ES.USER_GROUP_PROPERTY_NAME')], userGroups, 'id')
56+
}
57+
5258
// check the resource does not exist
5359
if (_.some(user[userResource.propertyName], [userResource.relateKey, relateId])) {
5460
logger.error(`Can't create existed ${resource} with the ${userResource.relateKey}: ${relateId}, userId: ${message.payload.userId}`)

0 commit comments

Comments
 (0)