Skip to content

Commit

Permalink
Merge pull request #153 from alphamanuscript/master
Browse files Browse the repository at this point in the history
v0.6.0
  • Loading branch information
habbes authored Oct 10, 2020
2 parents d5082d0 + c332461 commit e074cdc
Show file tree
Hide file tree
Showing 25 changed files with 176 additions and 91 deletions.
2 changes: 1 addition & 1 deletion server/src/core/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export async function bootstrap(config: AppConfig): Promise<App> {
webappBaseUrl: config.webappBaseUrl
});

const stats = new Statistics(db, { transactions });
const stats = new Statistics(db);

await users.createIndexes();
await transactions.createIndexes();
Expand Down
2 changes: 1 addition & 1 deletion server/src/core/payment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ export * from './types';
export { AtPaymentProvider, AT_PAYMENT_PROVIDER_NAME } from './at-payment-provider';
export { ManualPaymentProvider, MANUAL_PAYMENT_PROVIDER_NAME } from './manual-payment-provider';
export { FlutterwavePaymentProvider, FLUTTERWAVE_PAYMENT_PROVIDER_NAME } from './flutterwave-payment-provider';
export { Transactions, TransactionsArgs } from './transaction-service';
export { Transactions, TransactionsArgs, COLLECTION } from './transaction-service';
export { PaymentProviders } from './provider-registry';
13 changes: 1 addition & 12 deletions server/src/core/payment/transaction-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import * as messages from '../messages';
import * as validators from './validator';
import { EventBus } from '../event';

const COLLECTION = 'transactions';
export const COLLECTION = 'transactions';

export interface TransactionsArgs {
paymentProviders: PaymentProviderRegistry;
Expand Down Expand Up @@ -355,15 +355,4 @@ export class Transactions implements TransactionService {
private sendingProvider(): PaymentProvider {
return this.providers.getPreferredForSending();
}

async aggregate(pipeline: any[]): Promise<any[]> {
try {
const results = await this.collection.aggregate(pipeline, { allowDiskUse: true }).toArray();
return results;
}
catch(e) {
rethrowIfAppError(e);
throw createDbOpFailedError(e.message);
}
}
}
1 change: 0 additions & 1 deletion server/src/core/payment/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ export interface TransactionService {
* @param userId
*/
getUserBalance(userId: string): Promise<number>;
aggregate(pipeline: any[]): Promise<any[]>
}

export interface PaymentRequestResult {
Expand Down
31 changes: 17 additions & 14 deletions server/src/core/stat/stat-service.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import { Db, Collection } from 'mongodb';
import { createDbOpFailedError, rethrowIfAppError, createAppError, ErrorCode } from '../error';
import * as messages from '../messages';
import { TransactionService } from '../payment';
import { COLLECTION as TRX_COLLECTION } from '../payment';
import { COLLECTION as USERS_COLLECTION } from '../user';
import { StatsService, Stats } from './types';

const COLLECTION = 'stats';

export interface StatsArgs {
transactions: TransactionService
}

export class Statistics implements StatsService {
private db: Db;
private collection: Collection<Stats>;
private transactions: TransactionService;

constructor(db: Db, args: StatsArgs) {
constructor(db: Db) {
this.db = db;
this.collection = this.db.collection(COLLECTION);
this.transactions = args.transactions;
}

async get(): Promise<Stats> {
Expand All @@ -37,18 +33,18 @@ export class Statistics implements StatsService {

async update(): Promise<Stats> {
try {
const res = await this.transactions.aggregate([
const res = await this.db.collection(TRX_COLLECTION).aggregate([
{
$facet: {
numContributorsPipeline: [
{ $match: { type: 'donation', status: 'success'} },
{ $group: { _id: "$to" } },
{ $count: 'numContributors' }
],
numBeneficiariesPipeline: [
numRecipientsPipeline: [
{ $match: { type: 'distribution', status: 'success'} },
{ $group: { _id: "$to" } },
{ $count: 'numBeneficiaries' }
{ $count: 'numRecipients' }
],
totalContributedPipeline: [
{
Expand Down Expand Up @@ -77,24 +73,31 @@ export class Statistics implements StatsService {
],
}
}
]);
]).toArray();

let numContributors = 0;
let numBeneficiaries = 0;
let numRecipients = 0;
let totalContributed = 0;
let totalDistributed = 0;

if (res.length) {
numContributors = res[0].numContributorsPipeline.length ? res[0].numContributorsPipeline[0].numContributors : 0;
numBeneficiaries = res[0].numBeneficiariesPipeline.length ? res[0].numBeneficiariesPipeline[0].numBeneficiaries : 0;
numRecipients = res[0].numRecipientsPipeline.length ? res[0].numRecipientsPipeline[0].numRecipients : 0;
totalContributed = res[0].totalContributedPipeline.length ? res[0].totalContributedPipeline[0].totalContributed : 0;
totalDistributed = res[0].totalDistributedPipeline.length ? res[0].totalDistributedPipeline[0].totalDistributed : 0;
}

const beneficiariesRes = await this.db.collection(USERS_COLLECTION).aggregate([
{ $match: { roles: 'beneficiary'} },
{ $group: { _id: null, total: { $sum: 1 } } }
]).toArray();

const numBeneficiaries = beneficiariesRes.length ? beneficiariesRes[0].total : 0;

const updatedStats = await this.collection.findOneAndUpdate(
{ _id: 'stats' },
{
$set: { numContributors, totalContributed, numBeneficiaries, totalDistributed },
$set: { numContributors, totalContributed, numRecipients, totalDistributed, numBeneficiaries },
$currentDate: { updatedAt: true },
},
{ upsert: true, returnOriginal: false }
Expand Down
37 changes: 36 additions & 1 deletion server/src/core/stat/tests/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,39 @@ export const transactions = [
toExternal: true,
fromExternal: false
}
]
];

export const users = [
{
_id: 'id 1',
roles: ['donor']
},
{
_id: 'id 2',
roles: ['donor']
},
{
_id: 'id 3',
roles: ['beneficiary']
},
{
_id: 'id 4',
roles: ['beneficiary']
},
{
_id: 'id 5',
roles: ['beneficiary']
},
{
_id: 'id 6',
roles: ['beneficiary']
},
{
_id: 'id 7',
roles: ['beneficiary', 'middleman']
},
{
_id: '10',
roles: ['beneficiary']
}
];
24 changes: 8 additions & 16 deletions server/src/core/stat/tests/stat-service.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { transactions } from './fixtures';
import { transactions, users } from './fixtures';
import { Statistics } from '../stat-service';
import { Stats } from '../types';

Expand All @@ -7,6 +7,7 @@ import { createDbUtils, expectDatesAreClose } from '../../test-util';
const DB = '_social_relief_stat_service_tests_';
const STATS_COLLECTION = 'stats';
const TRANSACTION_COLLECTION = 'transactions';
const USERS_COLLECTION = 'users';

describe('stat-service tests', () => {
const dbUtils = createDbUtils(DB, STATS_COLLECTION);
Expand All @@ -22,27 +23,15 @@ describe('stat-service tests', () => {
beforeEach(async () => {
await dbUtils.resetCollectionWith([], STATS_COLLECTION);
await dbUtils.resetCollectionWith(transactions, TRANSACTION_COLLECTION);
await dbUtils.resetCollectionWith(users, USERS_COLLECTION);
});

function statsColl() {
return dbUtils.getCollection<Stats>(STATS_COLLECTION);
}

function transactionsColl() {
return dbUtils.getCollection<any>(TRANSACTION_COLLECTION)
}

function createDefaultService() {
const now = new Date();
const args: any = {
transactions: {
aggregate: jest.fn().mockImplementation((pipeline: any[]) => new Promise(async (resolve) => {
const results = await transactionsColl().aggregate(pipeline, { allowDiskUse: true }).toArray();
resolve(results);
}))
},
};
const service = new Statistics(dbUtils.getDb(), args);
const service = new Statistics(dbUtils.getDb());
return service;
}

Expand All @@ -67,6 +56,7 @@ describe('stat-service tests', () => {
const returnedStats = await createDefaultService().get();
const statsInDb = await statsColl().findOne({ _id: 'stats'});
expect(returnedStats.numContributors).toEqual(statsInDb.numContributors);
expect(returnedStats.numRecipients).toEqual(statsInDb.numRecipients);
expect(returnedStats.numBeneficiaries).toEqual(statsInDb.numBeneficiaries);
expect(returnedStats.totalContributed).toEqual(statsInDb.totalContributed);
expect(returnedStats.totalDistributed).toEqual(statsInDb.totalDistributed);
Expand All @@ -87,6 +77,7 @@ describe('stat-service tests', () => {
const statsDoc = await statsColl().findOne({ _id: 'stats' });
expect(statsDoc).toHaveProperty('_id');
expect(statsDoc).toHaveProperty('numContributors');
expect(statsDoc).toHaveProperty('numRecipients');
expect(statsDoc).toHaveProperty('numBeneficiaries');
expect(statsDoc).toHaveProperty('totalContributed');
expect(statsDoc).toHaveProperty('totalDistributed');
Expand All @@ -95,7 +86,8 @@ describe('stat-service tests', () => {
it('should return a stats doc with correct statistic figures', async () => {
const statsDoc = await createDefaultService().update();
expect(statsDoc.numContributors).toEqual(2);
expect(statsDoc.numBeneficiaries).toEqual(4);
expect(statsDoc.numRecipients).toEqual(4);
expect(statsDoc.numBeneficiaries).toEqual(6);
expect(statsDoc.totalContributed).toEqual(1970);
expect(statsDoc.totalDistributed).toEqual(880);
});
Expand Down
21 changes: 20 additions & 1 deletion server/src/core/stat/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
export interface Stats {
_id: string,
/**
* total number of donors who have made a donation
*/
numContributors: number,
/**
* total amount donated (refunds are deducted)
*/
totalContributed: number,
numBeneficiaries: number,
/**
* total number of beneficiaries who have received a donation
*/
numRecipients: number,
/**
* total amount distributed to beneficiaries
*/
totalDistributed: number,
/**
* total number of beneficiaries (whether they have received donations or not)
*/
numBeneficiaries: number,
/**
* when the stats were last updated
*/
updatedAt: Date
}

Expand Down
2 changes: 1 addition & 1 deletion server/src/core/user/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { User, UserCreateArgs, UserService, UserRole, UserInvitationEventData } from './types';
export { Users } from './user-service';
export { Users, COLLECTION } from './user-service';
5 changes: 0 additions & 5 deletions server/src/core/user/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,11 +244,6 @@ export interface UserService {
* @param user the donor to refund
*/
initiateRefund(user: string): Promise<Transaction>;
/**
*
* @param pipeline
*/
aggregate(pipeline: any[]): Promise<any[]>;
/**
* initiates a donation
* from anonymous user args.phone
Expand Down
13 changes: 1 addition & 12 deletions server/src/core/user/user-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { Invitation, InvitationService, InvitationCreateArgs } from '../invitati
import { EventBus, Event } from '../event';
import { SystemLockService } from '../system-lock';

const COLLECTION = 'users';
export const COLLECTION = 'users';
const TOKEN_COLLECTION = 'access_tokens';
const TOKEN_VALIDITY_MILLIS = 2 * 24 * 3600 * 1000; // 2 days
export const MAX_ALLOWED_REFUNDS = 3;
Expand Down Expand Up @@ -677,17 +677,6 @@ export class Users implements UserService {
}
}

async aggregate(pipeline: any[]): Promise<any[]> {
try {
const results = await this.collection.aggregate(pipeline, { allowDiskUse: true }).toArray();
return results;
}
catch(e) {
if (e instanceof AppError) throw e;
throw createDbOpFailedError(e.message);
}
}

public async donateAnonymously(args: UserDonateAnonymouslyArgs): Promise<Transaction> {
validators.validateDonateAnonymously(args);
const { amount, name, phone, email } = args;
Expand Down
34 changes: 34 additions & 0 deletions webapp/service-worker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// service-worker.js

workbox.core.setCacheNameDetails({ prefix: 'd4' })
//Change this value every time before you build
const LATEST_VERSION = 'v0.6.0'
self.addEventListener('activate', (event) => {
console.log(`%c ${LATEST_VERSION} `, 'background: #ddd; color: #0000ff')
if (caches) {
caches.keys().then((arr) => {
arr.forEach((key) => {
if (key.indexOf('d4-precache') < -1) {
caches.delete(key).then(() => console.log(`%c Cleared ${key}`, 'background: #333; color: #ff0000'))
} else {
caches.open(key).then((cache) => {
cache.match('version').then((res) => {
if (!res) {
cache.put('version', new Response(LATEST_VERSION, { status: 200, statusText: LATEST_VERSION }))
} else if (res.statusText !== LATEST_VERSION) {
caches.delete(key).then(() => console.log(`%c Cleared Cache ${LATEST_VERSION}`, 'background: #333; color: #ff0000'))
} else console.log(`%c Great you have the latest version ${LATEST_VERSION}`, 'background: #333; color: #00ff00')
})
})
}
})
})
}
})

workbox.core.skipWaiting()
workbox.core.clientsClaim()

self.__precacheManifest = [].concat(self.__precacheManifest || [])
workbox.precaching.suppressWarnings()
workbox.precaching.precacheAndRoute(self.__precacheManifest, {})
Binary file added webapp/src/assets/Beneficiary 1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/Beneficiary 2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/Beneficiary 3.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/beneficiary1.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/beneficiary2.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added webapp/src/assets/beneficiary3.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions webapp/src/components/donate-anonymously-modal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export default {
data() {
return {
donationInputs: {
amount: 1000,
amount: 2000,
name: '',
phone: '',
email: '',
Expand Down Expand Up @@ -137,7 +137,7 @@ export default {
...mapActions(['donateAnonymously']),
hideDialog() {
this.donationInputs = {
amount: 1000,
amount: 2000,
name: '',
phone: '',
email: '',
Expand Down
Loading

0 comments on commit e074cdc

Please sign in to comment.