Skip to content
Merged
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
74 changes: 74 additions & 0 deletions .github/workflows/qodo-review-comment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Comment on PR

on:
pull_request:
branches:
- dev
- main
types: [opened, synchronize]

jobs:
trigger-bot-review:
if: github.event.pull_request.draft == false
name: Trigger Bot Review
runs-on: ubuntu-latest
timeout-minutes: 10
env:
GITHUB_TOKEN: ${{ secrets.BOT_REVIEW_COMMENT_ACCESS_TOKEN }}
MAIN_BRANCH: main
steps:
- uses: actions/checkout@v2

- name: Add comment on PR creation on dev and main
if: false
uses: actions/github-script@v7
continue-on-error: true
timeout-minutes: 5
with:
github-token: ${{ env.GITHUB_TOKEN }}
script: |
const comments = ['/describe'];
for (const comment of comments) {
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}

- name: Add comment on PR creation to trigger bot review
if: github.event.action == 'opened' && github.base_ref == ${{ env.MAIN_BRANCH }}
uses: actions/github-script@v7
continue-on-error: true
timeout-minutes: 5
with:
github-token: ${{ env.GITHUB_TOKEN }}
script: |
const comments = ['/describe', '/review', '/improve'];
for (const comment of comments) {
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}

- name: Add comment on PR update
if: github.event.action == 'synchronize' && github.base_ref == ${{ env.MAIN_BRANCH }}
uses: actions/github-script@v7
continue-on-error: true
timeout-minutes: 5
with:
github-token: ${{ env.GITHUB_TOKEN }}
script: |
const comments = ['/review', '/improve'];
for (const comment of comments) {
github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: comment
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,14 @@ public void setSMSBridgeToBridgeConfigs() {

public void setSmsBridgeConfig(final Collection<SMSBridgeConfig> bridgeConfigurations) {
this.bridgeConfigurations.clear();
this.bridgeConfigurations = bridgeConfigurations;

// Add new configurations
if (bridgeConfigurations != null) {
for (SMSBridgeConfig config : bridgeConfigurations) {
config.setSMSBridge(this);
this.bridgeConfigurations.add(config);
}
}
}

public String generateApiKey() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.fineract.messagegateway.sms.providers.impl.africastalking;

import okhttp3.*;
import org.fineract.messagegateway.exception.MessageGatewayException;
import org.fineract.messagegateway.sms.domain.SMSBridge;
import org.fineract.messagegateway.sms.domain.SMSMessage;
Expand All @@ -11,85 +12,160 @@
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import okhttp3.FormBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import java.io.IOException;
import java.util.Collections;

@Service(value = "Africastalking")
public class AfricastalkingMessageProvider extends SMSProvider {
private static final Logger logger = LoggerFactory.getLogger(AfricastalkingMessageProvider.class);
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
private static final String SANDBOX_API_URL = "https://api.sandbox.africastalking.com/version1/messaging";
private static final String LIVE_API_URL = "https://api.africastalking.com/version1/messaging/bulk";

@Override
public void sendMessage(SMSBridge smsBridgeConfig, SMSMessage message) throws MessageGatewayException {
try {
OkHttpClient client = new OkHttpClient();
String from = smsBridgeConfig.getPhoneNo();
logger.info("Sending SMS from: {}", from);
if (from.equals("AFRICASTKNG")) {
from = null;
}
String username = smsBridgeConfig.getConfigValue("username");
String url = "https://api.africastalking.com/version1/messaging";
if (username.equals("sandbox")) {
url = "https://api.sandbox.africastalking.com/version1/messaging";
}
String mobile = smsBridgeConfig.getCountryCode() + message.getMobileNumber();
FormBody.Builder requestBodyBuilder = new FormBody.Builder()
.add("username", username)
.add("to", mobile)
.add("message", message.getMessage())
.add("bulkSMSMode", "1")
.add("enqueue", "0");

if (from != null) {
requestBodyBuilder.add("from", from);
String apiKey = smsBridgeConfig.getConfigValue("apiKey");

if (username.equals("sandbox")) {
sendMessageSandbox(client, smsBridgeConfig, message, mobile, apiKey);
} else {
sendMessageLive(client, smsBridgeConfig, message, mobile, apiKey);
}
} catch (IOException e) {
logger.error("An error occurred while sending the SMS: {}", e.getMessage(), e);
throw new MessageGatewayException("Failed to send SMS. Please check the logs for more details.");
}
}

private void sendMessageSandbox(OkHttpClient client, SMSBridge smsBridgeConfig, SMSMessage message, String mobile, String apiKey) throws IOException, MessageGatewayException {
String from = smsBridgeConfig.getPhoneNo();
logger.info("Sending SMS via sandbox from: {}", from);
if (from.equals("AFRICASTKNG")) {
from = null;
}

FormBody.Builder requestBodyBuilder = new FormBody.Builder()
.add("username", smsBridgeConfig.getConfigValue("username"))
.add("to", mobile)
.add("message", message.getMessage())
.add("bulkSMSMode", "1")
.add("enqueue", "0");

if (from != null) {
requestBodyBuilder.add("from", from);
}

FormBody requestBody = requestBodyBuilder.build();

// Log request details
logRequestDetails(SANDBOX_API_URL, "application/x-www-form-urlencoded",
String.format("username=%s, to=%s, message=%s, bulkSMSMode=1, enqueue=0%s",
smsBridgeConfig.getConfigValue("username"), mobile, message.getMessage(),
from != null ? ", from=" + from : ""));

// Build and execute request
Request request = buildRequest(SANDBOX_API_URL, apiKey, "application/x-www-form-urlencoded", requestBody);
executeRequest(client, request, message);
}

FormBody requestBody = requestBodyBuilder.build();

Request request = new Request.Builder()
.url(url)
.addHeader("apiKey", smsBridgeConfig.getConfigValue("apiKey"))
.addHeader("Content-Type", "application/x-www-form-urlencoded")
.addHeader("Accept", "application/json")
.post(requestBody)
.build();
Response response = client.newCall(request).execute();
if (response.isSuccessful()) {
String responseBody = response.body().string();
logger.info("SMS sent successfully. Response: {}", responseBody);

// Parse the response body
JSONObject responseJson = new JSONObject(responseBody);
JSONObject smsMessageData = responseJson.getJSONObject("SMSMessageData");
private void sendMessageLive(OkHttpClient client, SMSBridge smsBridgeConfig, SMSMessage message, String mobile, String apiKey) throws IOException, MessageGatewayException {
String senderId = smsBridgeConfig.getConfigValue("senderId");
if (senderId == null || senderId.trim().isEmpty()) {
throw new MessageGatewayException("senderId configuration is required for live environment");
}

logger.info("Sending SMS via live API with senderId: {}", senderId);

JSONObject jsonBody = new JSONObject()
.put("username", smsBridgeConfig.getConfigValue("username"))
.put("phoneNumbers", Collections.singletonList(mobile))
.put("message", message.getMessage())
.put("senderId", senderId);

RequestBody requestBody = RequestBody.create(JSON, jsonBody.toString());

// Log request details
logRequestDetails(LIVE_API_URL, "application/json", jsonBody.toString());

// Build and execute request
Request request = buildRequest(LIVE_API_URL, apiKey, "application/json", requestBody);
executeRequest(client, request, message);
}

/**
* Builds the HTTP request with appropriate headers
*/
private Request buildRequest(String url, String apiKey, String contentType, RequestBody requestBody) {
return new Request.Builder()
.url(url)
.addHeader("apiKey", apiKey)
.addHeader("Content-Type", contentType)
.addHeader("Accept", "application/json")
.post(requestBody)
.build();
}

/**
* Logs request details for debugging
*/
private void logRequestDetails(String url, String contentType, String payload) {
logger.info("Request URL: {}", url);
logger.info("Request Headers: Content-Type: {}, Accept: application/json", contentType);
logger.info("Request Payload: {}", payload);
}

/**
* Executes the request and processes the response
*/
private void executeRequest(OkHttpClient client, Request request, SMSMessage message) throws IOException, MessageGatewayException {
Response response = client.newCall(request).execute();
processResponse(response, message);
}

private void processResponse(Response response, SMSMessage message) throws IOException, MessageGatewayException {
ResponseBody responseBody = response.body();
if (responseBody == null) {
throw new MessageGatewayException("Empty response received from Africa's Talking API");
}

String responseString = responseBody.string();
if (response.isSuccessful()) {
logger.info("SMS sent successfully. Response: {}", responseString);

JSONObject responseJson = new JSONObject(responseString);
JSONObject smsMessageData = responseJson.getJSONObject("SMSMessageData");

if (smsMessageData.has("Recipients") && !smsMessageData.isNull("Recipients")) {
JSONArray recipients = smsMessageData.getJSONArray("Recipients");

if (recipients.length() > 0) {
if (!recipients.isEmpty()) {
JSONObject recipient = recipients.getJSONObject(0);

// Update the message with external ID and delivery status
String messageId = recipient.getString("messageId");
int statusCode = recipient.getInt("statusCode");
logger.info("Africa's Talking API response - MessageId: {}, StatusCode: {}", messageId, statusCode);
SmsMessageStatusType deliveryStatus = AfricastalkingStatus.smsStatus(statusCode);
logger.info("Mapped delivery status: {}", deliveryStatus);

message.setExternalId(messageId);
message.setDeliveryStatus(deliveryStatus.getValue());
} else {
String errorMessage = smsMessageData.getString("Message");
logger.error("Failed to send SMS. Error message: {}", errorMessage);
throw new MessageGatewayException("Failed to send SMS. Error message: " + errorMessage);
logger.error("Failed to send SMS. Empty recipients array. Error message: {}", errorMessage);
throw new MessageGatewayException("Failed to send SMS. Empty recipients array. Error message: " + errorMessage);
}
} else {
logger.error("Failed to send SMS. Response code: {}, Response body: {}", response.code(),
response.body().string());
throw new MessageGatewayException("Failed to send SMS. Please check the logs for more details.");
String errorMessage = smsMessageData.has("Message") ? smsMessageData.getString("Message") : "No Recipients array in response";
logger.error("Failed to send SMS. Missing Recipients data. Error message: {}", errorMessage);
throw new MessageGatewayException("Failed to send SMS. Missing Recipients data. Error message: " + errorMessage);
}
} catch (IOException e) {
logger.error("An error occurred while sending the SMS: {}", e.getMessage(), e);
throw new MessageGatewayException("Failed to send SMS. Please check the logs for more details.");
} else {
logger.error("Failed to send SMS. Response code: {}, Response body: {}", response.code(), responseString);
throw new MessageGatewayException("Failed to send SMS. Response code: " + response.code() + ", Response body: " + responseString);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ public static SmsMessageStatusType smsStatus(final Integer AfricastalkingStatus)
SmsMessageStatusType smsStatus = SmsMessageStatusType.PENDING;
switch (AfricastalkingStatus) {
case 100:
smsStatus = SmsMessageStatusType.PENDING;
smsStatus = SmsMessageStatusType.SENT;
break;
case 101:
smsStatus = SmsMessageStatusType.SENT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ private void assembleBridgeConfigParams(final SMSBridge bridge, final JsonArray
baseDataValidator.reset().parameter(SmsConstants.configname_paramname).value(configName).notBlank();
baseDataValidator.reset().parameter(SmsConstants.configvalue_paramname).value(configValue).notBlank();
final SMSBridgeConfig config = new SMSBridgeConfig(configName, configValue) ;
config.setSMSBridge(bridge);
configs.add(config) ;
}
bridge.setSmsBridgeConfig(configs) ;
Expand Down