diff --git a/server/src/core/app.ts b/server/src/core/app.ts
index aece4ded..071908f3 100644
--- a/server/src/core/app.ts
+++ b/server/src/core/app.ts
@@ -154,8 +154,8 @@ export function loadAppConfigFromEnv(env: { [key: string]: string }): AppConfig
distributionPeriodLength: (env.DISTRIBUTION_PERIOD_LENGTH && Number(env.DISTRIBUTION_PERIOD_LENGTH)) || 30,
distributionInterval: (env.DISTRIBUTION_INTERVAL) || `0 */2 * * * *`,
vettedDistributionInterval: (env.VETTED_DISTRIBUTION_INTERVAL) || `0 */5 * * * *`,
- dailyDistributionReportingInterval: (env.DAILY_DISTRIBUTION_REPORTING_INTERVAL) || `0 18 * * * *`,
- monthlyDistributionReportingInterval: (env.MONTHLY_DISTRIBUTION_REPORTING_INTERVAL) || `0 6 1 * * *`,
+ dailyDistributionReportingInterval: (env.DAILY_DISTRIBUTION_REPORTING_INTERVAL) || `0 0 18 * * *`,
+ monthlyDistributionReportingInterval: (env.MONTHLY_DISTRIBUTION_REPORTING_INTERVAL) || `0 0 10 1 * *`,
statsComputationInterval: (env.STATS_COMPUTATION_INTERVAL && Number(env.STATS_COMPUTATION_INTERVAL)) || 1,
googleClientId: env.GOOGLE_CLIENT_ID,
sendgridApiKey: env.SENDGRID_API_KEY || '',
diff --git a/server/src/core/distribution-report/distribution-report-service.ts b/server/src/core/distribution-report/distribution-report-service.ts
index 75df80e1..c579414b 100644
--- a/server/src/core/distribution-report/distribution-report-service.ts
+++ b/server/src/core/distribution-report/distribution-report-service.ts
@@ -13,7 +13,7 @@ export const REPORT_TYPE_DAILY = 'daily';
export const REPORT_TYPE_MONTHLY = 'monthly';
const DEFAULT_DONATION_AMOUNT = 2000;
-export interface DISTRIBUTION_REPORT_ENHANCED {
+export interface EnhancedDistributionReport {
donorId: string,
donor: User,
beneficiaryIds: string[],
@@ -116,7 +116,7 @@ export class DistributionReports implements DistributionReportService {
const amount = report.totalDistributedAmountFromDonor < DEFAULT_DONATION_AMOUNT ? DEFAULT_DONATION_AMOUNT : report.totalDistributedAmountFromDonor;
const donateLink = await this.args.links.getUserDonateLink(donor, amount);
- const reportEnhanced: DISTRIBUTION_REPORT_ENHANCED = {
+ const reportEnhanced: EnhancedDistributionReport = {
...report,
donorId: report.donor,
donor,
diff --git a/server/src/core/distribution-report/index.ts b/server/src/core/distribution-report/index.ts
index 774d6ccc..c11ca3f3 100644
--- a/server/src/core/distribution-report/index.ts
+++ b/server/src/core/distribution-report/index.ts
@@ -1,2 +1,2 @@
export * from './types';
-export { DistributionReports, REPORT_TYPE_DAILY, REPORT_TYPE_MONTHLY, DISTRIBUTION_REPORT_ENHANCED } from './distribution-report-service';
\ No newline at end of file
+export { DistributionReports, REPORT_TYPE_DAILY, REPORT_TYPE_MONTHLY, EnhancedDistributionReport } from './distribution-report-service';
\ No newline at end of file
diff --git a/server/src/core/link-generator/bitly-link-shortener.ts b/server/src/core/link-generator/bitly-link-shortener.ts
index 2793b8c8..ded06816 100644
--- a/server/src/core/link-generator/bitly-link-shortener.ts
+++ b/server/src/core/link-generator/bitly-link-shortener.ts
@@ -1,4 +1,5 @@
import axios from 'axios';
+import { LinkShortener } from './types';
import { rethrowIfAppError, createBitlyApiError, createLinkShorteningFailedError } from '../error';
export interface DeepLink {
@@ -34,10 +35,6 @@ export interface BitlyArgs {
apiLink: string;
}
-export interface LinkShortener {
- shortenLink(link: string): Promise;
-}
-
export class BitlyLinkShortener implements LinkShortener {
private apiKey: string;
private apiLink: string;
diff --git a/server/src/core/link-generator/index.ts b/server/src/core/link-generator/index.ts
index 4a63ac82..911b1446 100644
--- a/server/src/core/link-generator/index.ts
+++ b/server/src/core/link-generator/index.ts
@@ -1,2 +1,3 @@
+export * from './types';
export { Links } from './link-generator-service';
-export { BitlyLinkShortener, LinkShortener } from './bitly-link-shortener';
\ No newline at end of file
+export { BitlyLinkShortener } from './bitly-link-shortener';
diff --git a/server/src/core/link-generator/link-generator-service.ts b/server/src/core/link-generator/link-generator-service.ts
index 953cc1fc..1bdb632d 100644
--- a/server/src/core/link-generator/link-generator-service.ts
+++ b/server/src/core/link-generator/link-generator-service.ts
@@ -1,8 +1,8 @@
-import { LinkGeneratorService, DonateLinkArgs } from "./types";
-import { LinkShortener } from './bitly-link-shortener';
+import { LinkGeneratorService, DonateLinkArgs, LinkShortener } from './types';
import { User } from '../user';
import * as queryString from 'querystring';
import { rethrowIfAppError } from '../error';
+import { removePhoneCountryCode } from '../util';
interface LinksArgs {
baseUrl: string;
@@ -18,21 +18,43 @@ export class Links implements LinkGeneratorService {
async getUserDonateLink(user: User, amount: number, shorten: boolean = true): Promise {
const { name, email, phone } = user;
+ const shortPhone = removePhoneCountryCode(phone);
+
try {
- const longLink = this.getDonateLink({ name, email, phone, amount });
- if (shorten) {
- const shortLink = await this.args.shortener.shortenLink(longLink);
- return shortLink;
- }
+ const longLink = await this.getDonateLink({ name, email, phone: shortPhone, amount }, shorten);
return longLink;
}
catch (e) {
rethrowIfAppError(e);
}
}
- getDonateLink(args: DonateLinkArgs): string {
- const { name, email, phone, amount } = args;
- const nameQuery = queryString.stringify({ n: name}); // n=first%20last
- return `${this.args.baseUrl}?donate=true&${nameQuery}&e=${email}&p=${phone}&a=${amount}`;
+
+ async getDonateLink(args: DonateLinkArgs, shorten: boolean = true): Promise {
+ const query: any = {};
+
+ if (args.name) {
+ query.n = args.name;
+ }
+
+ if (args.email) {
+ query.e = args.email;
+ }
+
+ if (args.phone) {
+ query.p = args.phone;
+ }
+
+ if (args.amount) {
+ query.a = args.amount;
+ }
+
+ const encodedQuery = queryString.stringify(query);
+ const link = `${this.args.baseUrl}?donate=1&${encodedQuery}`;
+
+ if (shorten) {
+ return this.args.shortener.shortenLink(link);
+ }
+
+ return link;
}
}
\ No newline at end of file
diff --git a/server/src/core/link-generator/tests/link-generator-service.test.ts b/server/src/core/link-generator/tests/link-generator-service.test.ts
new file mode 100644
index 00000000..be3fe5a4
--- /dev/null
+++ b/server/src/core/link-generator/tests/link-generator-service.test.ts
@@ -0,0 +1,74 @@
+import { Links } from '../link-generator-service';
+import { User } from '../../user';
+import { LinkShortener } from '../types';
+
+describe('LinkGeneratorService tests', () => {
+ const baseUrl = 'https://test.com';
+ let shortener: LinkShortener;
+
+ function getService() {
+ return new Links({ shortener, baseUrl: baseUrl });
+ }
+
+ beforeEach(() => {
+ shortener = {
+ shortenLink: (link) => Promise.resolve(`shorten_${link}`)
+ };
+ });
+
+
+ describe('getDonateLink', () => {
+ test('should generate shortened donate link with arguments url encoded', async () => {
+ const service = getService();
+ const link = await service.getDonateLink({
+ name: 'John Doe',
+ phone: '711000222',
+ email: 'test@mailer.com',
+ amount: 3000
+ });
+
+ expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
+ });
+
+ test('should omit missing values from generated link', async () => {
+ const service = getService();
+ const link = await service.getDonateLink({
+ name: 'John Doe',
+ email: 'test@mailer.com'
+ });
+
+ expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com');
+ });
+
+ test('should not shorten link if shorten option is false', async () => {
+ const service = getService();
+ const link = await service.getDonateLink({
+ name: 'John Doe',
+ phone: '711000222',
+ email: 'test@mailer.com',
+ amount: 3000
+ }, false);
+
+ expect(link).toEqual('https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
+ });
+ });
+
+ describe('getUserDonateLink', () => {
+ test('should generate a shortened link for the specified user', async () => {
+ const service = getService();
+ const link = await service.getUserDonateLink({
+ _id: 'u1',
+ name: 'John Doe',
+ email: 'test@mailer.com',
+ phone: '254711000222',
+ addedBy: '',
+ donors: [],
+ roles: [],
+ createdAt: new Date(),
+ updatedAt: new Date()
+ }, 3000);
+
+ expect(link).toEqual('shorten_https://test.com?donate=1&n=John%20Doe&e=test%40mailer.com&p=711000222&a=3000');
+ });
+ });
+});
diff --git a/server/src/core/link-generator/types.ts b/server/src/core/link-generator/types.ts
index f210e17e..1163fcec 100644
--- a/server/src/core/link-generator/types.ts
+++ b/server/src/core/link-generator/types.ts
@@ -1,13 +1,44 @@
import { User } from '../user';
export interface DonateLinkArgs {
- name: string,
- email: string,
- phone: string,
- amount: number
+ /**
+ * name of the donor
+ */
+ name?: string,
+ /**
+ * email of the donor
+ */
+ email?: string,
+ /**
+ * phone number of the donor (should not include country code)
+ */
+ phone?: string,
+ /**
+ * The amount to donate
+ */
+ amount?: number,
+ /**
+ * Whether to shorten the link using a LinkShortener
+ */
+ shorten?: boolean
}
export interface LinkGeneratorService {
- getUserDonateLink(user: User, amount: number): Promise;
- getDonateLink(args: DonateLinkArgs): string;
-}
\ No newline at end of file
+ /**
+ * Generates a donation link for the specified user
+ * @param user the user to generate a link for
+ * @param amount the amount to donate
+ * @param shorten whether to shorten the link (defaults to true)
+ */
+ getUserDonateLink(user: User, amount: number, shorten?: boolean): Promise;
+ /**
+ * Generates a donation link with the specified arguments
+ * @param args
+ * @param shorten whether to shorten link (defaults to true)
+ */
+ getDonateLink(args: DonateLinkArgs, shorten?: boolean): Promise;
+}
+
+export interface LinkShortener {
+ shortenLink(link: string): Promise;
+}
diff --git a/server/src/core/message/message.ts b/server/src/core/message/message.ts
index 52978bb8..575752f5 100644
--- a/server/src/core/message/message.ts
+++ b/server/src/core/message/message.ts
@@ -1,4 +1,4 @@
-import { DISTRIBUTION_REPORT_ENHANCED } from '../distribution-report';
+import { EnhancedDistributionReport } from '../distribution-report';
import { DistributionReport, MonthlyDistributionReport } from '../payment';
import { User } from '../user';
import { extractFirstName } from '../util';
@@ -21,11 +21,11 @@ export function createDailyDistributionReportEmailMessage(report: DistributionRe
`;
}
-export function createMonthlyDistributionReportSmsMessageForContributingDonor(donorsReport: DISTRIBUTION_REPORT_ENHANCED, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
+export function createMonthlyDistributionReportSmsMessageForContributingDonor(donorsReport: EnhancedDistributionReport, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
return `Hello ${extractFirstName(donorsReport.donor.name)}, last month a total of Ksh ${systemWideReport.totalDonations} was donated by ${systemWideReport.distributionReports.length} Social Relief donors to ${systemWideReport.totalBeneficiaries} beneficiaries. Ksh ${donorsReport.totalDistributedAmountFromDonor} was sent from your donations to ${beneficiariesAndAmountReceived(donorsReport.beneficiaries, donorsReport.receivedAmount, 'sms')}. Thank you for your contribution. To donate again, click ${donateLink}`;
}
-export function createMonthlyDistributionReportEmailMessageForContributingDonor(donorsReport: DISTRIBUTION_REPORT_ENHANCED, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
+export function createMonthlyDistributionReportEmailMessageForContributingDonor(donorsReport: EnhancedDistributionReport, systemWideReport: MonthlyDistributionReport, donateLink: string): string {
return `
Hello ${extractFirstName(donorsReport.donor.name)},
Last month a total of Ksh ${systemWideReport.totalDonations} was donated by ${systemWideReport.distributionReports.length} Social Relief donors to ${systemWideReport.totalBeneficiaries} beneficiaries. Ksh ${donorsReport.totalDistributedAmountFromDonor} was sent from your donations to:
diff --git a/server/src/core/util/index.ts b/server/src/core/util/index.ts
index 7eeacaa2..4614f3fa 100644
--- a/server/src/core/util/index.ts
+++ b/server/src/core/util/index.ts
@@ -72,4 +72,14 @@ export async function verifyGoogleIdToken(token: string): Promise 711222333
+ * @param phone
+ */
+export function removePhoneCountryCode(phone: string) {
+ return phone.substr(3);
}
\ No newline at end of file
diff --git a/server/src/worker/monthly-distribution-report-worker.ts b/server/src/worker/monthly-distribution-report-worker.ts
index 631cfaaf..784642bd 100644
--- a/server/src/worker/monthly-distribution-report-worker.ts
+++ b/server/src/worker/monthly-distribution-report-worker.ts
@@ -1,6 +1,5 @@
import { App } from '../core';
import { CronJob } from 'cron';
-import { REPORT_TYPE_MONTHLY } from '../core/distribution-report';
export function runMonthlyDistributionReportingWorker(app: App, interval: string) {
const job = new CronJob(interval, async () => {