Skip to content

Commit

Permalink
ORV2-2294 Backend API - Create Credit Account (#1438)
Browse files Browse the repository at this point in the history
Co-authored-by: praju-aot <[email protected]>
Co-authored-by: Praveen Raju <[email protected]>
  • Loading branch information
3 people authored Jun 20, 2024
1 parent 110898f commit 080725f
Show file tree
Hide file tree
Showing 49 changed files with 2,074 additions and 46 deletions.
3 changes: 2 additions & 1 deletion charts/onroutebc/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ vehicles:
command:
- "sh"
- "-c"
- "source /vault/secrets/keycloak-{{.Values.global.vault.zone}} && source /vault/secrets/mssql-{{.Values.global.vault.zone}} && source /vault/secrets/payment-{{.Values.global.vault.zone}} && source /vault/secrets/vehicles-{{.Values.global.vault.zone}} && npm run start:prod"
- "source /vault/secrets/keycloak-{{.Values.global.vault.zone}} && source /vault/secrets/mssql-{{.Values.global.vault.zone}} && source /vault/secrets/payment-{{.Values.global.vault.zone}} && source /vault/secrets/vehicles-{{.Values.global.vault.zone}} && source /vault/secrets/cfs-{{.Values.global.vault.zone}} && npm run start:prod"
registry: '{{ .Values.global.registry }}'
repository: '{{ .Values.global.repository }}' # example, it includes registry and repository
image: vehicles
Expand Down Expand Up @@ -262,6 +262,7 @@ vehicles:
- "mssql-{{tpl $.Values.vault.zone $}}"
- "payment-{{tpl $.Values.vault.zone $}}"
- "vehicles-{{tpl $.Values.vault.zone $}}"
- "cfs-{{tpl $.Values.vault.zone $}}"
zone: "{{.Values.global.vault.zone}}"

dops:
Expand Down
14 changes: 13 additions & 1 deletion database/mssql/scripts/versions/v_28_ddl.sql
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,18 @@ VALUES (
)
GO

IF @@ERROR <> 0 SET NOEXEC ON
GO
INSERT [permit].[ORBC_CREDIT_ACCOUNT_ACTIVITY_TYPE] (
[CREDIT_ACCOUNT_ACTIVITY_TYPE],
[DESCRIPTION]
)
VALUES (
N'OPENED',
N'Account Opened'
)
GO

CREATE TABLE [permit].[ORBC_CREDIT_ACCOUNT_ACTIVITY] (
[ACTIVITY_ID] [int] IDENTITY(1, 1) NOT NULL,
[CREDIT_ACCOUNT_ID] [int] NOT NULL,
Expand Down Expand Up @@ -581,7 +593,7 @@ EXEC sys.sp_addextendedproperty
@level2name=N'DATE'
EXEC sys.sp_addextendedproperty
@name=N'MS_Description',
@value=N'Type of activity (e.g. ONHOLD, HOLDRMVD, CLOSED, REOPENED).' ,
@value=N'Type of activity (e.g. ONHOLD, HOLDRMVD, CLOSED, REOPENED, OPENED).' ,
@level0type=N'SCHEMA',
@level0name=N'permit',
@level1type=N'TABLE',
Expand Down
2 changes: 1 addition & 1 deletion database/mssql/test/versions/v_28_4_test.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
SET NOCOUNT ON

SELECT COUNT(*) FROM $(DB_NAME).[permit].[ORBC_CREDIT_ACCOUNT_ACTIVITY_TYPE]
WHERE CREDIT_ACCOUNT_ACTIVITY_TYPE IN ('ONHOLD','HOLDRMVD','CLOSED','REOPENED')
WHERE CREDIT_ACCOUNT_ACTIVITY_TYPE IN ('ONHOLD','HOLDRMVD','CLOSED','REOPENED', 'OPENED')
2 changes: 1 addition & 1 deletion database/mssql/test/versions/v_28_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ else
fi

TEST_28_4_RESULT=$(/opt/mssql-tools/bin/sqlcmd -U ${USER} -P "${PASS}" -S ${SERVER} -v DB_NAME=${DATABASE} -h -1 -i ${TESTS_DIR}/v_28_4_test.sql | xargs)
if [[ $TEST_28_4_RESULT -eq 4 ]]; then
if [[ $TEST_28_4_RESULT -eq 5 ]]; then
echo "Test 28.4 passed: Correct number of credit account activity types inserted"
else
echo "******** Test 28.4 failed: Incorrect number of credit account activity types inserted"
Expand Down
3 changes: 3 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ services:
ORBC_SERVICE_ACCOUNT_CLIENT_SECRET: ${ORBC_SERVICE_ACCOUNT_CLIENT_SECRET}
ORBC_SERVICE_ACCOUNT_AUDIENCE: ${ORBC_SERVICE_ACCOUNT_AUDIENCE}
BCGOV_FAX_EMAIL: ${BCGOV_FAX_EMAIL}
CFS_CREDIT_ACCOUNT_URL: ${CFS_CREDIT_ACCOUNT_URL}
CFS_CREDIT_ACCOUNT_CLIENT_ID: ${CFS_CREDIT_ACCOUNT_CLIENT_ID}
CFS_CREDIT_ACCOUNT_CLIENT_SECRET: ${CFS_CREDIT_ACCOUNT_CLIENT_SECRET}
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5000/"]
interval: 1m30s
Expand Down
1 change: 1 addition & 0 deletions dops/src/guard/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class RolesGuard implements CanActivate {
context.getHandler(),
context.getClass(),
]);
// Guard is invoked regardless of the decorator being actively called
if (!roles) {
return true;
}
Expand Down
3 changes: 3 additions & 0 deletions vehicles/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ ENV GL_CODE ${GL_CODE}
ENV PAYBC_REDIRECT ${PAYBC_REDIRECT}
ENV ORBC_SERVICE_ACCOUNT_CLIENT_ID ${ORBC_SERVICE_ACCOUNT_CLIENT_ID}
ENV ORBC_SERVICE_ACCOUNT_AUDIENCE ${ORBC_SERVICE_ACCOUNT_AUDIENCE}
ENV CFS_CREDIT_ACCOUNT_URL ${CFS_CREDIT_ACCOUNT_URL}
ENV CFS_CREDIT_ACCOUNT_CLIENT_ID ${CFS_CREDIT_ACCOUNT_CLIENT_ID}
ENV CFS_CREDIT_ACCOUNT_CLIENT_SECRET ${CFS_CREDIT_ACCOUNT_CLIENT_SECRET}
ENV BCGOV_FAX_EMAIL ${BCGOV_FAX_EMAIL}

# Copy production files from build
Expand Down
4 changes: 2 additions & 2 deletions vehicles/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import { ApplicationModule } from './modules/permit-application-payment/applicat
import { PaymentModule } from './modules/permit-application-payment/payment/payment.module';
import { PermitReceiptDocumentModule } from './modules/permit-application-payment/permit-receipt-document/permit-receipt-document.module';
import { ShoppingCartModule } from './modules/shopping-cart/shopping-cart.module';
import { CreditAccount } from './modules/credit-account/entities/credit-account.entity';
import { CreditAccountModule } from './modules/credit-account/credit-account.module';

const envPath = path.resolve(process.cwd() + '/../');

Expand Down Expand Up @@ -98,7 +98,7 @@ const envPath = path.resolve(process.cwd() + '/../');
PermitReceiptDocumentModule,
ApplicationModule, //! Application Module should be imported before PermitModule to avoid URI conflict
PermitModule,
CreditAccount,
CreditAccountModule,
FeatureFlagsModule,
],
controllers: [AppController],
Expand Down
22 changes: 22 additions & 0 deletions vehicles/src/common/decorator/is-feature-flag-enabled.decorator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Reflector } from '@nestjs/core';

/**
* Decorator to check if a specific feature flag is enabled.
*
* The feature flag can be used to conditionally enable or disable parts of the application
* depending on the current configuration or environment setup.
*
* Usage:
*
* ```typescript
* @IsFeatureFlagEnabled('featureName')
* async someFunction() {
* // function implementation
* }
* ```
*
* @param {string} flagName - The name of the feature flag to check.
* @returns {MethodDecorator} The method decorator to be applied.
*/

export const IsFeatureFlagEnabled = Reflector.createDecorator<string>();
1 change: 1 addition & 0 deletions vehicles/src/common/enum/cache-key.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export enum CacheKey {
EMAIL_TEMPLATE_ORBC_STYLE = 'EMAIL_TEMPLATE_ORBC_STYLE',
CHES_ACCESS_TOKEN = 'CHES_ACCESS_TOKEN',
CDOGS_ACCESS_TOKEN = 'CDOGS_ACCESS_TOKEN',
CREDIT_ACCOUNT_ACCESS_TOKEN = 'CREDIT_ACCOUNT_ACCESS_TOKEN',
PAYMENT_CARD_TYPE = 'PAYMENT_CARD_TYPE',
PAYMENT_METHOD_TYPE = 'PAYMENT_METHOD_TYPE',
FEATURE_FLAG_TYPE = 'FEATURE_FLAG_TYPE',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export enum CreditAccountActivityType {
ACCOUNT_HOLD_REMOVED = 'HOLDRMVD',
ACCOUNT_CLOSED = 'CLOSED',
ACCOUNT_REOPENED = 'REOPENED',
ACCOUNT_OPENED = 'OPENED',
}
22 changes: 22 additions & 0 deletions vehicles/src/common/enum/credit-account-limit.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export const CreditAccountLimit = {
PREPAID: 'PREPAID',
500: '500',
1000: '1000',
2000: '2000',
5000: '5000',
7500: '7500',
10000: '10000',
15000: '15000',
20000: '20000',
30000: '30000',
40000: '40000',
50000: '50000',
60000: '60000',
70000: '70000',
80000: '80000',
90000: '90000',
100000: '100000',
} as const;

export type CreditAccountLimitType =
(typeof CreditAccountLimit)[keyof typeof CreditAccountLimit];
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
export enum CreditAccountStatusType {
ACCOUNT_ON_HOLD = 'ONHOLD',
ACCOUNT_ACTIVE = 'ACTIVE',
ACCOUNT_SETUP = 'SETUP',
ACCOUNT_SETUP_FAIL = 'SETUP_FAIL',
ACCOUNT_CLOSED = 'CLOSED',
}
4 changes: 4 additions & 0 deletions vehicles/src/common/enum/credit-accounts.enum.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export enum CreditAccountUserType {
ACCOUNT_HOLDER = 'HOLDER',
ACCOUNT_USER = 'USER',
}
1 change: 1 addition & 0 deletions vehicles/src/common/enum/gov-common-services.enum.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum GovCommonServices {
COMMON_HOSTED_EMAIL_SERVICE = 'CHES',
COMMON_DOCUMENT_GENERATION_SERVICE = 'CDOGS',
CREDIT_ACCOUNT_SERVICE = 'CREDIT_ACCOUNT',
}
2 changes: 2 additions & 0 deletions vehicles/src/common/enum/roles.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,6 @@ export enum Role {
READ_SUSPEND = 'ORBC-READ-SUSPEND',
WRITE_SUSPEND = 'ORBC-WRITE-SUSPEND',
SEND_NOTIFICATION = 'ORBC-SEND-NOTIFICATION',
WRITE_CREDIT_ACCOUNT = 'ORBC-WRITE-CREDIT-ACCOUNT',
READ_CREDIT_ACCOUNT = 'ORBC-READ-CREDIT-ACCOUNT',
}
61 changes: 61 additions & 0 deletions vehicles/src/common/guard/feature-flag.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import {
CanActivate,
ExecutionContext,
Inject,
Injectable,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Cache } from 'cache-manager';
import { IsFeatureFlagEnabled } from '../decorator/is-feature-flag-enabled.decorator';
import { CacheKey } from '../enum/cache-key.enum';
import { FeatureFlagValue } from '../enum/feature-flag-value.enum';
import { getMapFromCache } from '../helper/cache.helper';

/**
* @class FeatureFlagGuard
* @description A guard that checks if a specific feature flag is enabled before allowing access to a route.
* @implements {CanActivate}
*
* @constructor
* @param {Reflector} reflector - A utility for accessing decorated metadata on classes and methods.
* @param {Cache} cacheManager - A cache manager instance for retrieving feature flag values.
*
* @method canActivate
* @description Determines if the current request can proceed based on the feature flag status.
* @param {ExecutionContext} context - The context of the current execution (e.g., request).
* @returns {Promise<boolean>} - A promise that resolves to true if the feature flag is enabled, otherwise false.
*/
@Injectable()
export class FeatureFlagGuard implements CanActivate {
constructor(
private reflector: Reflector,
@Inject(CACHE_MANAGER)
private readonly cacheManager: Cache,
) {}

/**
* Determines if the current request can proceed based on the feature flag status.
*
* @method canActivate
* @param {ExecutionContext} context - The context of the current execution (e.g., request).
* @returns {Promise<boolean>} - A promise that resolves to true if the feature flag is enabled, otherwise false.
*/
async canActivate(context: ExecutionContext): Promise<boolean> {
const featureFlagKey = this.reflector.getAllAndOverride<string>(
IsFeatureFlagEnabled,
[context.getHandler(), context.getClass()],
);
// Guard is invoked regardless of the decorator being actively called
if (!featureFlagKey) return Promise.resolve(true);
const featureFlags = await getMapFromCache(
this.cacheManager,
CacheKey.FEATURE_FLAG_TYPE,
);
const isFeatureEnabled =
featureFlags?.[featureFlagKey] &&
(featureFlags[featureFlagKey] as FeatureFlagValue) ===
FeatureFlagValue.ENABLED;
return isFeatureEnabled;
}
}
1 change: 1 addition & 0 deletions vehicles/src/common/guard/roles.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export class RolesGuard implements CanActivate {
context.getHandler(),
context.getClass(),
]);
// Guard is invoked regardless of the decorator being actively called
if (!roles) {
return true;
}
Expand Down
16 changes: 16 additions & 0 deletions vehicles/src/common/helper/credit-account.helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { CreditAccount } from '../../modules/credit-account/entities/credit-account.entity';
import { CreditAccountStatusType } from '../enum/credit-account-status-type.enum';

export const isActiveCreditAccount = (creditAccount: CreditAccount) => {
return (
creditAccount?.creditAccountStatusType ===
CreditAccountStatusType.ACCOUNT_ACTIVE
);
};

export const isClosedCreditAccount = (creditAccount: CreditAccount) => {
return (
creditAccount?.creditAccountStatusType ===
CreditAccountStatusType.ACCOUNT_CLOSED
);
};
80 changes: 62 additions & 18 deletions vehicles/src/common/helper/gov-common-services.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,30 +8,28 @@ import { GovCommonServicesToken } from '../interface/gov-common-services-token.i
import { CacheKey } from '../enum/cache-key.enum';
import { TOKEN_EXPIRY_BUFFER } from '../constants/api.constant';

const logger = new Logger('GocCommonServicesHelper');
const logger = new Logger('GovCommonServicesHelper');

/**
* Retrieves an access token for the specified government common service.
*
* The function first checks the cache for an existing token. If a valid token is found, it is returned.
* If there is no valid token in the cache, a new token is requested from the token URL using client credentials.
* The new token is then cached with an expiration time before being returned.
*
* @param {GovCommonServices} govCommonServices - The specific government common service for which the access token is required.
* @param {HttpService} httpService - The HTTP service used to make the token request.
* @param {Cache} cacheManager - The cache manager used to store and retrieve cached tokens.
* @returns {Promise<string>} A promise that resolves to the access token.
* @throws {InternalServerErrorException} If an error occurs while acquiring the token.
*/
export async function getAccessToken(
govCommonServices: GovCommonServices,
httpService: HttpService,
cacheManager: Cache,
) {
let tokenCacheKey: CacheKey = undefined;
let tokenUrl: string = undefined;
let username: string = undefined;
let password: string = undefined;
if (govCommonServices === GovCommonServices.COMMON_HOSTED_EMAIL_SERVICE) {
tokenCacheKey = CacheKey.CHES_ACCESS_TOKEN;
tokenUrl = process.env.CHES_TOKEN_URL;
username = process.env.CHES_CLIENT_ID;
password = process.env.CHES_CLIENT_SECRET;
} else if (
govCommonServices === GovCommonServices.COMMON_DOCUMENT_GENERATION_SERVICE
) {
tokenCacheKey = CacheKey.CDOGS_ACCESS_TOKEN;
tokenUrl = process.env.CDOGS_TOKEN_URL;
username = process.env.CDOGS_CLIENT_ID;
password = process.env.CDOGS_CLIENT_SECRET;
}
const { tokenCacheKey, tokenUrl, username, password } =
getTokenCredentials(govCommonServices);

const tokenFromCache: GovCommonServicesToken =
await cacheManager.get(tokenCacheKey);
Expand Down Expand Up @@ -86,3 +84,49 @@ export async function getAccessToken(

return token.access_token;
}

/**
* Retrieves the token credentials required for accessing government common services.
*
* @param {GovCommonServices} govCommonServices - The specific government common service for which the credentials are needed.
* @returns {Object} An object containing the following properties:
* - tokenCacheKey: {CacheKey} The key used to cache the token.
* - tokenUrl: {string} The URL used to request the token.
* - username: {string} The username/client ID for the token request.
* - password: {string} The password/client secret for the token request.
*/
function getTokenCredentials(govCommonServices: GovCommonServices): {
tokenCacheKey: CacheKey;
tokenUrl: string;
username: string;
password: string;
} {
let tokenCacheKey: CacheKey = undefined;
let tokenUrl: string = undefined;
let username: string = undefined;
let password: string = undefined;
switch (govCommonServices) {
case GovCommonServices.COMMON_HOSTED_EMAIL_SERVICE:
tokenCacheKey = CacheKey.CHES_ACCESS_TOKEN;
tokenUrl = process.env.CHES_TOKEN_URL;
username = process.env.CHES_CLIENT_ID;
password = process.env.CHES_CLIENT_SECRET;
break;
case GovCommonServices.COMMON_DOCUMENT_GENERATION_SERVICE:
tokenCacheKey = CacheKey.CDOGS_ACCESS_TOKEN;
tokenUrl = process.env.CDOGS_TOKEN_URL;
username = process.env.CDOGS_CLIENT_ID;
password = process.env.CDOGS_CLIENT_SECRET;
break;
case GovCommonServices.CREDIT_ACCOUNT_SERVICE:
tokenCacheKey = CacheKey.CREDIT_ACCOUNT_ACCESS_TOKEN;
tokenUrl = `${process.env.CFS_CREDIT_ACCOUNT_URL}/oauth/token`;
username = process.env.CFS_CREDIT_ACCOUNT_CLIENT_ID;
password = process.env.CFS_CREDIT_ACCOUNT_CLIENT_SECRET;
break;

default:
break;
}
return { tokenCacheKey, tokenUrl, username, password };
}
1 change: 0 additions & 1 deletion vehicles/src/modules/auth/jwt.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export class JwtStrategy extends PassportStrategy(Strategy) {
companyId = associatedCompanies?.length
? associatedCompanies?.at(0)
: companyId;

if (
!associatedCompanies.includes(companyId) ||
associatedCompanyMetadataList?.at(0)?.isSuspended
Expand Down
Loading

0 comments on commit 080725f

Please sign in to comment.