Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/brac-dev-deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: Dev Build & Deploy Entity Service (BRAC)

on:
push:
branches:
- develop

env:
AWS_REGION: ${{ secrets.AWS_REGION }}
ECR_REPOSITORY: ${{ secrets.ECR_REPOSITORY_BRAC }}
AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}

jobs:
build-and-deploy:
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v4

# =========================
# AWS Authentication
# =========================
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}

# =========================
# Login to Amazon ECR
# =========================
- name: Login to Amazon ECR
uses: aws-actions/amazon-ecr-login@v2

# =========================
# Build & Push Image
# =========================
- name: Build and Push Docker Image to ECR
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ env.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}:latest-brac
${{ env.AWS_ACCOUNT_ID }}.dkr.ecr.${{ env.AWS_REGION }}.amazonaws.com/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
# =========================
# Deploy on Server
# =========================
- name: Deploy Stack
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.HOST_NAME_DEV }}
username: ${{ secrets.USERNAME_DEV }}
key: ${{ secrets.SSH_KEY_DEV }}
port: ${{ secrets.PORT_DEV }}
script: |
set -e
# Export AWS variables
export AWS_REGION="${{ secrets.AWS_REGION }}"
export AWS_ACCOUNT_ID="${{ secrets.AWS_ACCOUNT_ID }}"
cd ${{ secrets.TARGET_DIR_DEV }}
# Backup old env if exists
if [ -f .env ]; then
mv .env .env-bkp
fi
# Write env safely (MULTILINE SAFE)
cat << 'EOF' > .env
${{ secrets.DEV_ENV }}
EOF
# Login to ECR (non-interactive)
aws ecr get-login-password --region "$AWS_REGION" \
| docker login --username AWS \
--password-stdin "$AWS_ACCOUNT_ID.dkr.ecr.$AWS_REGION.amazonaws.com"
./deploy.sh
13 changes: 13 additions & 0 deletions src/constants/interface-routes/configs.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,19 @@
}
]
},
{
"sourceRoute": "/entity-management/v1/entities/createUserAsAnEntity",
"type": "POST",
"priority": "MUST_HAVE",
"inSequence": false,
"orchestrated": false,
"targetPackages": [
{
"basePackageName": "entity",
"packageName": "elevate-entity-management"
}
]
},
{
"sourceRoute": "/entity-management/v1/entities/update",
"type": "POST",
Expand Down
138 changes: 138 additions & 0 deletions src/controllers/v1/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,144 @@ module.exports = class Entities extends Abstract {
})
}

/**
* Add entities after bulk import from user service.
* @api {POST} /entity/api/v1/entities/createUserAsAnEntity
* @apiVersion 1.0.0
* @apiName createUserAsAnEntity
* @apiGroup Entities
* @apiHeader {String} X-authenticated-user-token Authenticity token
* @apiHeader {String} internal-access-token Internal access token
* @param {Object} req - Event data from user service bulk create.
* @returns {JSON} - Added entity information.
*/
createUserAsAnEntity(req) {
return new Promise(async (resolve, reject) => {
try {
const eventData = req.body

// Get organizations from event data - check direct, oldValues, and newValues
const organizations =
eventData.organizations ||
eventData.oldValues?.organizations ||
eventData.newValues?.organizations ||
[]

// Determine entity type based on roles in organizations array
// Priority: first check for org_admin role, then check for user role
let entityType = null
if (organizations.length > 0 && organizations[0].roles && Array.isArray(organizations[0].roles)) {
const roles = organizations[0].roles
const roleTitles = roles.map((role) => role.title || role.label)

// Check for org_admin role first (higher priority)
if (roleTitles.includes(CONSTANTS.common.ORG_ADMIN)) {
entityType = 'linkageChampion'
} else if (roleTitles.includes(CONSTANTS.common.USER_ROLE)) {
// Check for user role
entityType = 'participant'
}
}

// If no supported role found, skip the operation
if (!entityType) {
return resolve({
message: CONSTANTS.apiResponses.ENTITY_TYPE_NOT_SUPPORTED,
result: null,
})
}

// Extract required data from event - check direct, oldValues, and newValues
const externalId = eventData.entityId ? eventData.entityId.toString() : null
const name = eventData.name || eventData.oldValues?.name || eventData.newValues?.name || null

if (!externalId || !name) {
return reject({
status: HTTP_STATUS_CODE.bad_request.status,
message: CONSTANTS.apiResponses.MISSING_REQUIRED_FIELDS,
errorObject: { externalId, name },
})
}

// Construct userDetails from event data - check direct, oldValues, and newValues
const tenantId =
eventData.tenant_code ||
eventData.oldValues?.tenant_code ||
eventData.newValues?.tenant_code ||
null
const orgId = organizations.length > 0 ? organizations[0].id : null
const userId =
eventData.created_by ||
eventData.id ||
eventData.userId ||
eventData.oldValues?.id ||
eventData.newValues?.id ||
null

if (!tenantId || !orgId) {
return reject({
status: HTTP_STATUS_CODE.bad_request.status,
message: CONSTANTS.apiResponses.MISSING_TENANT_OR_ORG_INFO,
errorObject: { tenantId, orgId },
})
}

const userDetails = {
userInformation: {
userId: userId ? userId.toString() : 'SYSTEM',
},
tenantAndOrgInfo: {
tenantId: tenantId,
orgId: [orgId.toString()],
},
}

// Check if entity with same externalId already exists
const existingEntity = await entitiesQueries.findOne(
{
'metaInformation.externalId': externalId,
entityType: entityType,
tenantId: tenantId,
},
{ _id: 1, metaInformation: 1, entityType: 1 }
)

if (existingEntity) {
// Entity already exists, return existing entity
return resolve({
message: CONSTANTS.apiResponses.ENTITY_ALREADY_EXISTS,
result: existingEntity,
})
}

// Prepare query parameters
const queryParams = {
type: entityType,
}

// Prepare request body for entity creation
const entityBody = {
externalId: externalId,
name: name,
}

// Call 'entitiesHelper.add' to perform the entity addition operation
let result = await entitiesHelper.add(queryParams, entityBody, userDetails)

return resolve({
message: CONSTANTS.apiResponses.ENTITY_ADDED,
result: result,
})
} catch (error) {
return reject({
status: error.status || HTTP_STATUS_CODE.internal_server_error.status,
message: error.message || HTTP_STATUS_CODE.internal_server_error.message,
errorObject: error,
})
}
})
}

/**
* List of entities by location ids.
* @api {get} v1/entities/list List all entities based locationIds
Expand Down
4 changes: 4 additions & 0 deletions src/generics/constants/api-responses.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,8 @@ module.exports = {
ENTITIES_DELETED_SUCCESSFULLY: 'ENTITIES_DELETED_SUCCESSFULLY',
ADMIN_ROLE_REQUIRED: 'Access denied: Admin role required',
NOT_A_VALID_MONGOID: 'externalId cannot be a Mongo ObjectId',
ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS',
MISSING_TENANT_OR_ORG_INFO: 'MISSING_TENANT_OR_ORG_INFO',
MISSING_REQUIRED_FIELDS: 'MISSING_REQUIRED_FIELDS',
ENTITY_TYPE_NOT_SUPPORTED: 'ENTITY_TYPE_NOT_SUPPORTED',
}
2 changes: 2 additions & 0 deletions src/generics/constants/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
'/entities/bulkCreate',
'/entities/bulkUpdate',
'/entities/add',
'/entities/createUserAsAnEntity',
'/entities/update',
'/entityTypes/create',
'/entityTypes/update',
Expand Down Expand Up @@ -44,6 +45,7 @@ module.exports = {
ADMIN_ROLE: 'admin',
ORG_ADMIN: 'org_admin',
TENANT_ADMIN: 'tenant_admin',
USER_ROLE: 'user',
SERVER_TIME_OUT: 5000,
GUEST_URLS: ['/entities/details', '/entities/entityListBasedOnEntityType', 'entities/subEntityList'],
ALL: 'ALL',
Expand Down
4 changes: 3 additions & 1 deletion src/generics/middleware/authenticator.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,9 @@ module.exports = async function (req, res, next, token = '') {
)

if (performInternalAccessTokenCheck) {
if (req.headers['internal-access-token'] !== process.env.INTERNAL_ACCESS_TOKEN) {
const internalAccessToken = req.headers['internal_access_token'] || req.headers['internal-access-token']

if (internalAccessToken !== process.env.INTERNAL_ACCESS_TOKEN) {
rspObj.errCode = CONSTANTS.apiResponses.TOKEN_MISSING_CODE
rspObj.errMsg = CONSTANTS.apiResponses.TOKEN_MISSING_MESSAGE
rspObj.responseCode = HTTP_STATUS_CODE['unauthorized'].status
Expand Down
3 changes: 3 additions & 0 deletions src/models/entities.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ module.exports = {
externalId: { type: String, index: true },
name: { type: String, index: true },
targetedEntityTypes: { type: Array },
status: { type: String, default: 'ACTIVE', index: true },
onBoardingProjectId: { type: String },
IDPProjectId: { type: String },
},
childHierarchyPath: Array,
userId: {
Expand Down
1 change: 0 additions & 1 deletion src/module/entities/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -1278,7 +1278,6 @@ module.exports = class UserProjectsHelper {
let result = await entitiesQueries.getAggregate(aggregateData)
count = result?.[0]?.totalCount?.[0]?.count || 0


if (aggregateStaging == true) {
if (!Array.isArray(result) || !(result.length > 0)) {
throw {
Expand Down
5 changes: 5 additions & 0 deletions src/module/entities/validator/v1.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ module.exports = (req) => {
registryMappingUpload: function () {
req.checkQuery('entityType').exists().withMessage('required entity type')
},
createUserAsAnEntity: function () {
// Minimal validation for internal endpoint receiving event data
req.checkBody('entity').exists().withMessage('entity field is required')
req.checkBody('entityId').exists().withMessage('entityId field is required')
},
}

if (entitiesValidator[req.params.method]) {
Expand Down