Skip to content

Commit

Permalink
Merge pull request #125 from chamil321/gov-sms-incop
Browse files Browse the repository at this point in the history
Add API for bulk SMS recipient registration
  • Loading branch information
chamil321 committed Jul 25, 2020
2 parents a08e2f4 + c83d57a commit b3109e0
Show file tree
Hide file tree
Showing 7 changed files with 228 additions and 7 deletions.
77 changes: 73 additions & 4 deletions distributor/src/distributor/messenger.bal
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ballerina/config;
import ballerina/io;
import ballerina/log;
import ballerina/stringutils;
import chamil/govsms;
Expand Down Expand Up @@ -85,8 +86,9 @@ function validate(string mobileNo) returns string|error {

boolean number = stringutils:matches(mobile, "^[0-9]*$");
if !number {
return error(ERROR_REASON, message = "Invalid mobile number. Given mobile number contains non numeric " +
"characters: " + mobile);
string errorMsg = "Invalid mobile number. Given mobile number contains non numeric characters: " + mobile;
log:printError(errorMsg);
return error(ERROR_REASON, message = errorMsg);
}

if (mobile.startsWith("0") && mobile.length() == 10) {
Expand All @@ -96,6 +98,7 @@ function validate(string mobileNo) returns string|error {
return mobile;
}
// Allow only the local mobile numbers to register via public API. International number are avoided.
log:printError("Invalid mobile number : " + mobile);
return error(ERROR_REASON, message = "Invalid mobile number. Resend the request as follows: If the " +
"mobile no is 0771234567, send POST request to \'/sms\' with JSON payload " +
"\'{\"username\":\"myuser\", \"mobile\":\"0771234567\"}\'");
Expand All @@ -106,7 +109,7 @@ function validate(string mobileNo) returns string|error {
# + username - The recipient username
# + mobileNo - The recipient number
# + return - The status of registration or operation error
function registerAsSMSRecipient(string username, string mobileNo) returns string|error {
function registerSmsRecipient(string username, string mobileNo) returns string|error {

if mobileSubscribers.hasKey(username) {
string errMsg = "Registration failed: username:" + username + " is already registered with mobile:" + mobileNo;
Expand All @@ -131,7 +134,7 @@ function registerAsSMSRecipient(string username, string mobileNo) returns string
# + username - The recipient username
# + mobileNo - The recipient number
# + return - The status of deregistration or operation error
function unregisterAsSMSRecipient(string username, string mobileNo) returns string|error {
function unregisterSmsRecipient(string username, string mobileNo) returns string|error {
string result = "";
if mobileSubscribers.hasKey(username) && mobileSubscribers.get(username) == mobileNo {
result = "Successfully unregistered: username:" + username + " mobile:" + mobileSubscribers.remove(username);
Expand All @@ -150,3 +153,69 @@ function unregisterAsSMSRecipient(string username, string mobileNo) returns stri
}
return result;
}

function unregisterAllSMSRecipient() returns error? {
mobileSubscribers = {};
_ = check dbClient->update(DELETE_RECIPIENT_TABLE);
}

function readRecipients(io:ReadableByteChannel|error payload) returns @tainted Recipient[]|error {

io:ReadableByteChannel rbChannel = check payload;

io:ReadableCharacterChannel rch = new (rbChannel, "UTF8");
var result = rch.readJson();
closeReadableChannel(rch);
if (result is error) {
string logMessage = "Error occurred while reading json: malformed Recipient[]";
return error(ERROR_REASON, message = logMessage, cause = result);
}

Recipient[]|error recipientArray = Recipient[].constructFrom(<json>result);
if recipientArray is error {
string logMessage = "Error occurred while converting json: malformed Recipient[]";
return error(ERROR_REASON, message = logMessage, cause = recipientArray);
}
return recipientArray;
}

function closeReadableChannel(io:ReadableCharacterChannel rc) {
var result = rc.close();
if (result is error) {
log:printError("Error occurred while closing character stream", result);
}
}

function validateAllRecipients(Recipient[] recipients) returns error? {
string errMsg = "invalid recipient mobile no: ";
boolean erroneous = false;
foreach var user in recipients {
string|error validatedNo = validate(user.mobile);
if validatedNo is error {
erroneous = true;
errMsg = errMsg + user.username + ":" + user.mobile + ", ";
} else {
user.mobile = validatedNo;
}
}
if erroneous {
return error(ERROR_REASON, message = errMsg.substring(0, errMsg.length() - 2));
}
}

function registerAllSMSRecipients(Recipient[] recipients) returns error? {
string errMsg = "";
boolean erroneous = false;
foreach var user in recipients {
string|error status = registerSmsRecipient(user.username.trim(), user.mobile);
if status is error {
erroneous = true;
errMsg = errMsg + user.username + ":" + user.mobile + ", ";
} else {
log:printInfo(status);
}
}
if erroneous {
return error(ERROR_REASON, message = errMsg.substring(0, errMsg.length() - 2));
}
}
1 change: 1 addition & 0 deletions distributor/src/distributor/save.bal
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const INSERT_RECIPIENT = "INSERT INTO smsRecipients (username, mobileNo) VALUES
const DELETE_RECIPIENT = "DELETE FROM smsRecipients WHERE username = ?";
const SELECT_RECIPIENT_DATA = "SELECT * FROM smsRecipients";
const DROP_RECIPIENT_TABLE = "DROP TABLE smsRecipients";
const DELETE_RECIPIENT_TABLE = "DELETE FROM smsRecipients";

jdbc:Client dbClient = new ({
url: config:getAsString("eclk.hub.db.url"),
Expand Down
10 changes: 10 additions & 0 deletions distributor/src/distributor/tests/resources/contact1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"username":"newuser1",
"mobile":"0711234561"
},
{
"username":"newuser2",
"mobile":"0711234562"
}
]
10 changes: 10 additions & 0 deletions distributor/src/distributor/tests/resources/contact2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"username":"newuser1",
"mobile":"+00771234562"
},
{
"username":"newuser2",
"mobile":"0711234562"
}
]
10 changes: 10 additions & 0 deletions distributor/src/distributor/tests/resources/contact3.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[
{
"user":"newuser1",
"mobi":"0771234562"
},
{
"usrname":"newuser2",
"mobie":"0711234562"
}
]
73 changes: 72 additions & 1 deletion distributor/src/distributor/tests/sms_publisher_test.bal
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ballerina/test;
// [b7a.users.test]
// password="password"
// scopes="ECAdmin"
@test:Config { enable: false }
@test:Config { enable: false, after: "testResetRecipients" }
function testSubscriberRegistration() {
auth:OutboundBasicAuthProvider outboundBasicAuthProvider1 = new({
username: "test",
Expand Down Expand Up @@ -96,6 +96,77 @@ function testSubscriberRegistration() {
} else {
test:assertFail(msg = "Failed to call the endpoint:");
}

// Test bulk success registration
req = new;
req.setFileAsPayload("src/distributor/tests/resources/contact1.json");
response = httpEndpoint->post("/sms/addall", req);
if (response is http:Response) {
var result = response.getTextPayload();
if (result is string) {
test:assertEquals(result, "Successfully registered all");
} else {
test:assertFail(msg = "Invalid response message:");
}
} else {
test:assertFail(msg = "Failed to call the endpoint:");
}

// Test bulk registration with invalid nos
req = new;
req.setFileAsPayload("src/distributor/tests/resources/contact2.json");
response = httpEndpoint->post("/sms/addall", req);
if (response is http:Response) {
var result = response.getTextPayload();
if (result is string) {
test:assertEquals(result, "Validation failed: invalid recipient mobile no: newuser1:+00771234562");
} else {
test:assertFail(msg = "Invalid response message:");
}
} else {
test:assertFail(msg = "Failed to call the endpoint:");
}

// Test bulk registration with malformed JSON
req = new;
req.setFileAsPayload("src/distributor/tests/resources/contact3.json");
response = httpEndpoint->post("/sms/addall", req);
if (response is http:Response) {
var result = response.getTextPayload();
if (result is string) {
test:assertTrue(stringutils:contains(result, "Error occurred while converting json: malformed Recipient"));
} else {
test:assertFail(msg = "Invalid response message:");
}
} else {
test:assertFail(msg = "Failed to call the endpoint:");
}
}

@test:Config { before: "testSubscriberRegistration" }
function testResetRecipients() {
auth:OutboundBasicAuthProvider outboundBasicAuthProvider1 = new({
username: "test",
password: config:getAsString("b7a.users.test.password")
});
http:BasicAuthHandler outboundBasicAuthHandler1 = new(outboundBasicAuthProvider1);
http:Client httpEndpoint = new("http://localhost:9090", {
auth: {
authHandler: outboundBasicAuthHandler1
}
});
http:Request req = new;
var response = httpEndpoint->delete("/sms/reset", req);
if (response is http:Response) {
var result = response.getTextPayload();
if (result is string) {
test:assertEquals(result, "Successfully unregistered all");
} else {
test:assertFail(msg = "Invalid response message:");
}
} else {
test:assertFail(msg = "Failed to call the endpoint:");
}
}

@test:Config {}
Expand Down
54 changes: 52 additions & 2 deletions distributor/src/distributor/website.bal
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,48 @@ service mediaWebsite on mediaListener {
}

// If the load is high, we might need to sync following db/map update
string|error status = registerAsSMSRecipient(smsRecipient.username.trim(), <string> validatedNo);
string|error status = registerSmsRecipient(smsRecipient.username.trim(), <string> validatedNo);
if status is error {
return caller->internalServerError(<@untainted> <string> status.detail()?.message);
}
return caller->ok(<@untainted> <string> status);
}

// This API enables registering mobile nos via a file(binary payload). Make sure the data is structured
// as an array of Recipients and the content type is `application/octet-stream`.
// [
// { "username":"newuser1", "mobile":"0771234567" },
// { "username":"newuser2", "mobile":"0771234568" },
// { "username":"newuser3", "mobile":"0771234569" }
// ]
@http:ResourceConfig {
path: "/sms/addall",
methods: ["POST"],
consumes: ["application/octet-stream"],
auth: {
scopes: ["ECAdmin"]
}
}
resource function smsBulkRegistration (http:Caller caller, http:Request req) returns error? {
Recipient[]|error recipient = readRecipients(req.getByteChannel());
if recipient is error {
log:printError("Invalid input", recipient);
return caller->badRequest(<@untainted> recipient.toString());
}
Recipient[] smsRecipient = <Recipient[]> recipient;
error? validatedNo = validateAllRecipients(smsRecipient);
if validatedNo is error {
return caller->badRequest("Validation failed: " + <@untainted string> validatedNo.detail()?.message);
}

error? status = registerAllSMSRecipients(smsRecipient);
if status is error {
return caller->internalServerError("Registration failed: " + <@untainted string> status.detail()?.message);
}

return caller->ok("Successfully registered all");
}

@http:ResourceConfig {
path: "/sms",
methods: ["DELETE"],
Expand All @@ -228,13 +263,28 @@ service mediaWebsite on mediaListener {
}

// If the load is high, we might need to sync following db/map update
string|error status = unregisterAsSMSRecipient(smsRecipient.username.trim(), <string> validatedNo);
string|error status = unregisterSmsRecipient(smsRecipient.username.trim(), <string> validatedNo);
if status is error {
return caller->internalServerError(<@untainted string> status.detail()?.message);
}
return caller->ok(<@untainted string> status);
}

@http:ResourceConfig {
path: "/sms/reset",
methods: ["DELETE"],
auth: {
scopes: ["ECAdmin"]
}
}
resource function resetSmsRecipients(http:Caller caller, http:Request req) returns error? {
error? status = unregisterAllSMSRecipient();
if status is error {
return caller->internalServerError(<string> status.detail()?.message);
}
return caller->ok("Successfully unregistered all");
}

// May have to move to a separate service.
@http:ResourceConfig {
webSocketUpgrade: {
Expand Down

0 comments on commit b3109e0

Please sign in to comment.