Skip to content

Commit

Permalink
Merge pull request #8 from choxx/6-be-uci-odk-adapter-shift-to-cdac-s…
Browse files Browse the repository at this point in the history
…ervice-instead-of-uci

Support to send messages via chosen SMS adapter (CDAC / UCI)
  • Loading branch information
choxx authored Jan 25, 2023
2 parents 49ecb61 + 488ccb4 commit 4893b37
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 61 deletions.
22 changes: 22 additions & 0 deletions Dockerfile.local
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:16

# Create app directory
WORKDIR /app

# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package.json ./
COPY yarn.lock ./
COPY prisma ./prisma/

# Install app dependencies
RUN yarn install
COPY . .
RUN npx prisma generate
RUN yarn run build
COPY . .

# install hasura cli
RUN curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash

EXPOSE 3000
CMD [ "yarn", "run", "start:dev" ]
99 changes: 99 additions & 0 deletions docker-compose.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
version: '3.7'

services:
db:
image: postgres:12
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
networks:
- my-network
ports:
- ${POSTGRES_PORT}:5432
volumes:
- pgdata:/var/lib/postgresql/data

gql:
image: hasura/graphql-engine:v2.7.0
ports:
- ${HASURA_PORT}:8080
env_file:
- .env
environment:
HASURA_GRAPHQL_DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
HASURA_GRAPHQL_ENABLE_CONSOLE: "true"
HASURA_GRAPHQL_ADMIN_SECRET: ${HASURA_ADMIN_SECRET}
WEBHOOK_URL: http://web:${PORT}/submit
networks:
- my-network
depends_on:
- db
restart: always

redis:
image: redis:alpine
# ports:
# - 127.0.0.1:${QUEUE_PORT}:6379
command: ["redis-server", "--appendonly", "yes", "--replica-read-only", "no"]
networks:
- my-network
volumes:
- ./redis-data:/data
- ./redis.conf:/usr/local/etc/redis/redis.conf
restart: on-failure

redis-ui:
# container_name: redis-ui
image: redislabs/redisinsight:latest
restart: always
networks:
- my-network
volumes:
- redisinsight:/db
ports:
- 8088:8001

# cdac:
# image: samagragovernance/cdac-service:latest
# restart: always
# networks:
# - my-network
# environment:
# USERNAME: "${USERNAME}"
# SENDER_ID: "${SENDER_ID}"
# PASSWORD: "${PASSWORD}"
# SECURE_KEY: "${SECURE_KEY}"
# ports:
# - "${CDAC_PORT}:8080"
# healthcheck:
# test: ["CMD", "curl", "-f", "http://cdac:8080/api"]
# interval: 2s
# timeout: 5s
# retries: 20

web:
build:
dockerfile: Dockerfile.local
context: .
networks:
- my-network
env_file:
- .env
depends_on:
- redis
- gql
- db
ports:
- '${PORT}:3000'
restart: always
volumes:
- ./src:/app/src # using local volume

volumes:
pgdata:
redisinsight:
driver: local
networks:
my-network:
driver: bridge
27 changes: 22 additions & 5 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ services:

redis:
image: redis:alpine
ports:
- 127.0.0.1:${QUEUE_PORT}:6379
# ports:
# - 127.0.0.1:${QUEUE_PORT}:6379
command: ["redis-server", "--appendonly", "yes", "--replica-read-only", "no"]
networks:
- my-network
Expand All @@ -53,10 +53,27 @@ services:
- redisinsight:/db
ports:
- 8088:8001


# cdac:
# image: samagragovernance/cdac-service:latest
# restart: always
# networks:
# - my-network
# environment:
# USERNAME: "${USERNAME}"
# SENDER_ID: "${SENDER_ID}"
# PASSWORD: "${PASSWORD}"
# SECURE_KEY: "${SECURE_KEY}"
# ports:
# - "${CDAC_PORT}:8080"
# healthcheck:
# test: ["CMD", "curl", "-f", "http://cdac:8080/api"]
# interval: 2s
# timeout: 5s
# retries: 20

web:
build:
context: .
image: samagragovernance/odk-uci-adapter:latest
networks:
- my-network
env_file:
Expand Down
17 changes: 16 additions & 1 deletion sample.env
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ GQL_HOST=
GQL_URL=
GQL_HEADERS={"content-type":"application/json","x-hasura-admin-secret": "xxxx"}

# UCI variables
UCI_500_ALLOWED=false # if allowed then we'll consider 500s from UCI as success
UCI_URL=
UCI_ADAPTER_ID=
Expand All @@ -32,4 +33,18 @@ DATABASE_URL=

# for /health endpoint
HASURA_URL=http://gql:8080
APP_URL=http://localhost:3000
APP_URL=http://localhost:3000

# SMS Adapter
SMS_ADAPTER_TYPE= # CDAC or UCI

# CDAC Variables
CDAC_SERVICE_URL=http://cdac:8081

# if CDAC service is deployed via current project's docker-compose
CDAC_HOST=cdac
CDAC_PORT=8081
USERNAME=
SENDER_ID=
PASSWORD=
SECURE_KEY=
11 changes: 11 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import { TerminusModule } from '@nestjs/terminus';
import { PrismaHealthIndicator } from '../prisma/prisma.health';
import { RedisModule } from '@liaoliaots/nestjs-redis';
import { RedisHealthModule } from '@liaoliaots/nestjs-redis-health';
import { SmsAdapterType, SmsAdapterTypeToken } from './sms.templates';
import { UciService } from './sms-adapter/uci/uci.service';
import { CdacService } from './sms-adapter/cdac/cdac.service';

@Module({
imports: [
Expand Down Expand Up @@ -85,6 +88,14 @@ import { RedisHealthModule } from '@liaoliaots/nestjs-redis-health';
AnnouncementProcessor,
HomeworkProcessor,
PrismaHealthIndicator,
{
provide: SmsAdapterTypeToken,
useClass:
process.env.SMS_ADAPTER_TYPE == SmsAdapterType.CDAC
? CdacService
: UciService,
},
],
exports: [SmsAdapterTypeToken],
})
export class AppModule {}
56 changes: 7 additions & 49 deletions src/app.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { HttpService } from '@nestjs/axios';
import { HttpException, Injectable, Logger } from '@nestjs/common';
import { HttpException, Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { catchError, lastValueFrom, map } from 'rxjs';
import { PrismaService } from './prisma.service';
import { SmsAdapterTypeToken } from './sms.templates';
import { SmsAdapterInterface } from './sms-adapter/sms-adapter.interface';

@Injectable()
export class AppService {
Expand All @@ -14,6 +16,8 @@ export class AppService {
private prisma: PrismaService,
private readonly httpService: HttpService,
private configService: ConfigService,
@Inject(SmsAdapterTypeToken)
private readonly smsService: SmsAdapterInterface,
) {
this.adapterId = this.configService.get<string>('UCI_ADAPTER_ID');
this.UCI_500_ALLOWED =
Expand Down Expand Up @@ -51,54 +55,7 @@ export class AppService {
templateId: string,
payload: string,
): Promise<any> {
this.logger.debug(
`Processing registerSms() callback: ${JSON.stringify([
phone,
templateId,
payload,
])}`,
);
const data = {
adapterId: this.adapterId,
to: {
userID: phone,
deviceType: 'PHONE',
meta: {
templateId: templateId,
},
},
payload: {
text: payload,
},
};
return await lastValueFrom(
this.httpService
.post(`${this.configService.get<string>('UCI_URL')}/message/send`, data)
.pipe(
map((response: any) => {
this.logger.debug(
`Processed registerSms() SUCCESS: ${JSON.stringify(
response.data,
)}`,
);
return response.data;
}),
catchError((e) => {
this.logger.error(
`Processing registerSms() FAILURE: ${JSON.stringify(
e.response.data,
)}`,
);
if (this.UCI_500_ALLOWED && e.response.status == 500) {
// simply just resolve the promise as success even in case of 500s
return new Promise((resolve) => {
resolve(e.response.data);
});
}
throw new HttpException(e.response.error, e.response.status); // or else throw exception
}),
),
);
return this.smsService.sendSms(phone, templateId, payload);
}

async updateSubmissionStatus(id, status, remarks = ''): Promise<any> {
Expand Down Expand Up @@ -127,6 +84,7 @@ export class AppService {
this.logger.debug(
`Processing insertSmsTrackEntry() callback: ${JSON.stringify(data)}`,
);
data.status = data.status || 'UNKNOWN'; // marking the status as unknown in case nothing received
return this.prisma.sms_track.create({
data: data,
});
Expand Down
21 changes: 21 additions & 0 deletions src/sms-adapter/cdac/cdac.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';
import { CdacService } from './cdac.service';
import { ConfigModule } from '@nestjs/config';
import { HttpModule } from '@nestjs/axios';

describe('CdacService', () => {
let service: CdacService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [ConfigModule, HttpModule],
providers: [CdacService],
}).compile();

service = module.get<CdacService>(CdacService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
68 changes: 68 additions & 0 deletions src/sms-adapter/cdac/cdac.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { HttpException, Injectable, Logger } from '@nestjs/common';
import { SmsAdapterInterface } from '../sms-adapter.interface';
import { ConfigService } from '@nestjs/config';
import { HttpService } from '@nestjs/axios';
import { catchError, lastValueFrom, map } from 'rxjs';

@Injectable()
export class CdacService implements SmsAdapterInterface {
private readonly cdacServiceUrl;
protected readonly logger = new Logger(CdacService.name); // logger instance
constructor(
private configService: ConfigService,
private httpService: HttpService,
) {
this.logger.log(`${CdacService.name} initialized..`);
this.cdacServiceUrl = configService.get<string>('CDAC_SERVICE_URL');
}

async sendSms(phone: string, templateId: string, payload: string) {
this.logger.debug(
`Processing registerSms() callback: ${JSON.stringify([
phone,
templateId,
payload,
])}`,
);
const params = new URLSearchParams({
message: payload,
mobileNumber: phone,
templateid: templateId,
});
const url = `${
this.cdacServiceUrl
}/api/send_single_unicode_sms?${params.toString()}`;
console.log(url);
return await lastValueFrom(
this.httpService.get(url).pipe(
map((response: any) => {
let messageId = response.data.toString();
messageId = messageId.trim('402,MsgID = ');
messageId = messageId.trim('hpgovt-hpssa');
const resp = {
timestamp: new Date(),
status: 200,
error: null,
message: response.data.toString(),
path: '/api/send_single_unicode_sms',
result: {
messageId: messageId,
},
};
this.logger.debug(
`Processed registerSms() SUCCESS: ${JSON.stringify(resp)}`,
);
return resp;
}),
catchError((e) => {
this.logger.error(
`Processing registerSms() FAILURE: ${JSON.stringify(
e.response.data,
)}`,
);
throw new HttpException(e.response.error, e?.response?.status || 500); // or else throw exception
}),
),
);
}
}
Loading

0 comments on commit 4893b37

Please sign in to comment.