Skip to content

Commit

Permalink
microcredit list borrowers
Browse files Browse the repository at this point in the history
  • Loading branch information
Bernardo Vieira committed May 4, 2023
1 parent 8eaf7fa commit 7659c6f
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 50 deletions.
36 changes: 36 additions & 0 deletions packages/api/src/controllers/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { services } from '@impactmarket/core';
import { Response } from 'express';

import { standardResponse } from '../../../utils/api';
import { ListBorrowersRequestSchema } from '../../../validators/microcredit';
import { ValidatedRequest } from '../../../utils/queryValidator';
import { RequestWithUser } from '../../../middlewares/core';

class MicroCreditController {
private microCreditService: services.MicroCredit.List;
constructor() {
this.microCreditService = new services.MicroCredit.List();
}

// list borrowers using a loan manager account
listBorrowers = (
req: RequestWithUser & ValidatedRequest<ListBorrowersRequestSchema>,
res: Response
) => {
if (req.user === undefined) {
standardResponse(res, 400, false, '', {
error: {
name: 'USER_NOT_FOUND',
message: 'User not identified!',
},
});
return;
}
this.microCreditService
.listBorrowers({ ...req.query, addedBy: req.user.address })
.then((r) => standardResponse(res, 200, true, r))
.catch((e) => standardResponse(res, 400, false, '', { error: e }));
};
}

export { MicroCreditController };
11 changes: 11 additions & 0 deletions packages/api/src/routes/v2/microcredit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Router } from 'express';

import list from './list';

export default (app: Router): void => {
const route = Router();

app.use('/microcredit', route);

list(route);
};
31 changes: 31 additions & 0 deletions packages/api/src/routes/v2/microcredit/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Router } from 'express';

import { MicroCreditController } from '../../../controllers/v2/microcredit/list';
import { listBorrowersValidator } from '../../../validators/microcredit';
import { authenticateToken } from '../../../middlewares';

export default (route: Router): void => {
const controller = new MicroCreditController();

/**
* @swagger
*
* /microcredit/borrowers:
* get:
* tags:
* - "microcredit"
* summary: "Get List of Borrowers by manager"
* responses:
* "200":
* description: OK
* security:
* - api_auth:
* - "write:modify":
*/
route.get(
'/borrowers/:query?',
authenticateToken,
listBorrowersValidator,
controller.listBorrowers
);
};
26 changes: 26 additions & 0 deletions packages/api/src/validators/microcredit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Joi } from 'celebrate';

import { defaultSchema } from './defaultSchema';
import {
ContainerTypes,
createValidator,
ValidatedRequestSchema,
} from '../utils/queryValidator';

const validator = createValidator();

const queryListBorrowersSchema = defaultSchema.object({
offset: Joi.number().optional(),
limit: Joi.number().optional(),
});

interface ListBorrowersRequestSchema extends ValidatedRequestSchema {
[ContainerTypes.Query]: {
offset?: number;
limit?: number;
};
}

const listBorrowersValidator = validator.query(queryListBorrowersSchema);

export { listBorrowersValidator, ListBorrowersRequestSchema };
6 changes: 4 additions & 2 deletions packages/core/src/services/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { listLessons, listLevels } from './learnAndEarn/list';
import { startLesson } from './learnAndEarn/start';
import { webhook } from './learnAndEarn/syncRemote';
import { total } from './learnAndEarn/userData';
import MicrocreditService from './microcredit';
import Protocol from './protocol';
import * as MicroCredit from './microcredit';
import * as storage from './storage';
import StoryServiceV2 from './story/index';
import * as ubi from './ubi';
Expand All @@ -29,5 +30,6 @@ export {
Email,
StoryServiceV2,
learnAndEarn,
MicrocreditService,
MicroCredit,
Protocol,
};
23 changes: 2 additions & 21 deletions packages/core/src/services/microcredit/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,3 @@
import { queries } from '../../subgraph';
import List from './list';

export default class MicrocreditService {
public getGlobalData = async (): Promise<any> => {
const subgraphData = await queries.microcredit.getGlobalData();

// TODO: calculate applications { totalApplications, inReview }

const estimatedMaturity = 0; // (paid back in the past 3 months / 3 / current debt)
const avgBorrowedAmount = Math.round(subgraphData.totalBorrowed / subgraphData.activeBorrowers);
const apr = 0; // (paid back past 7 months - borrowed past 7 months) / borrowed past 7 months / 7 * 12

return {
totalApplications: 0,
inReview: 0,
estimatedMaturity,
avgBorrowedAmount,
apr,
...subgraphData,
}
}
}
export { List };
54 changes: 54 additions & 0 deletions packages/core/src/services/microcredit/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { models } from '../../database';
import { getBorrowers } from '../../subgraph/queries/microcredit';
import { getAddress } from '@ethersproject/address';

function mergeArrays(arr1: any[], arr2: any[], key: string) {
const map = new Map(arr1.map((item) => [item[key], item]));
arr2.forEach((item) => {
map.has(item[key])
? Object.assign(map.get(item[key]), item)
: map.set(item[key], item);
});
return Array.from(map.values());
}

export default class MicroCreditList {
public listBorrowers = async (query: {
offset?: number;
limit?: number;
addedBy?: string;
}): Promise<
{
address: string;
firstName: string;
lastName: string;
avatarMediaPath: string;
loans: {
amount: string;
period: number;
dailyInterest: number;
claimed: number;
repayed: string;
lastRepayment: number;
}[];
}[]
> => {
// get borrowers loans from subgraph
const borrowers = await getBorrowers(query);

// get borrowers profile from database
const userProfile = await models.appUser.findAll({
attributes: ['address', 'firstName', 'lastName', 'avatarMediaPath'],
where: {
address: borrowers.map((b) => getAddress(b.id)),
},
});

// merge borrowers loans and profile
return mergeArrays(
borrowers.map((b) => ({ address: getAddress(b.id), ...b })),
userProfile.map((u) => u.toJSON()),
'address'
);
};
}
154 changes: 128 additions & 26 deletions packages/core/src/subgraph/queries/microcredit.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import { intervalsInSeconds } from '../../types';
import { redisClient } from '../../database';
import { axiosMicrocreditSubgraph } from '../config';

type Asset = {
id: string,
asset: string,
amount: string,
}
id: string;
asset: string;
amount: string;
};

export const getGlobalData = async (): Promise<{
totalBorrowed: number,
currentDebt: number,
paidBack: number,
earnedInterest: number,
activeBorrowers: number,
totalDebitsRepaid: number,
liquidityAvailable: number,
totalBorrowed: number;
currentDebt: number;
paidBack: number;
earnedInterest: number;
activeBorrowers: number;
totalDebitsRepaid: number;
liquidityAvailable: number;
}> => {
try {
const graphqlQuery = {
Expand Down Expand Up @@ -59,13 +61,13 @@ export const getGlobalData = async (): Promise<{
data: {
data: {
microCredit: {
borrowed: Asset[],
debit: Asset[],
repaid: Asset[],
interest: Asset[],
borrowers: number,
repayments: number,
liquidity: Asset[],
borrowed: Asset[];
debit: Asset[];
repaid: Asset[];
interest: Asset[];
borrowers: number;
repayments: number;
liquidity: Asset[];
};
};
};
Expand All @@ -75,16 +77,116 @@ export const getGlobalData = async (): Promise<{
const microCredit = response.data?.data.microCredit;

return {
totalBorrowed: microCredit.borrowed && microCredit.borrowed.length ? parseFloat(microCredit.borrowed[0].amount) : 0,
currentDebt: microCredit.debit && microCredit.debit.length ? parseFloat(microCredit.debit[0].amount) : 0,
paidBack: microCredit.repaid && microCredit.repaid.length ? parseFloat(microCredit.repaid[0].amount) : 0,
earnedInterest: microCredit.interest && microCredit.interest.length ? parseFloat(microCredit.interest[0].amount) : 0,
totalBorrowed:
microCredit.borrowed && microCredit.borrowed.length
? parseFloat(microCredit.borrowed[0].amount)
: 0,
currentDebt:
microCredit.debit && microCredit.debit.length
? parseFloat(microCredit.debit[0].amount)
: 0,
paidBack:
microCredit.repaid && microCredit.repaid.length
? parseFloat(microCredit.repaid[0].amount)
: 0,
earnedInterest:
microCredit.interest && microCredit.interest.length
? parseFloat(microCredit.interest[0].amount)
: 0,
activeBorrowers: microCredit.borrowers ? microCredit.borrowers : 0,
totalDebitsRepaid: microCredit.repayments ? microCredit.repayments : 0,
liquidityAvailable: microCredit.liquidity && microCredit.liquidity.length ? parseFloat(microCredit.liquidity[0].amount) : 0,
totalDebitsRepaid: microCredit.repayments
? microCredit.repayments
: 0,
liquidityAvailable:
microCredit.liquidity && microCredit.liquidity.length
? parseFloat(microCredit.liquidity[0].amount)
: 0,
};

} catch (error) {
throw new Error(error);
}
};
};

export const getBorrowers = async (query: {
offset?: number;
limit?: number;
addedBy?: string;
}): Promise<
{
id: string;
loans: {
amount: string;
period: number;
dailyInterest: number;
claimed: number;
repayed: string;
lastRepayment: number;
}[];
}[]
> => {
const graphqlQuery = {
operationName: 'borrowers',
query: `query borrowers {
borrowers(
first: ${query.limit ? query.limit : 10}
skip: ${query.offset ? query.offset : 0}
) {
id
loans(
where: {
addedBy: "${
query.addedBy ? query.addedBy.toLowerCase() : ''
}"
}
) {
amount
period
dailyInterest
claimed
repayed
lastRepayment
}
}
}`,
};

const cacheResults = await redisClient.get(graphqlQuery.query);

if (cacheResults) {
return JSON.parse(cacheResults);
}

const response = await axiosMicrocreditSubgraph.post<
any,
{
data: {
data: {
borrowers: {
id: string;
loans: {
amount: string;
period: number;
dailyInterest: number;
claimed: number;
repayed: string;
lastRepayment: number;
}[];
}[];
};
};
}
>('', graphqlQuery);

const borrowers = response.data?.data.borrowers.filter(
(b) => b.loans.length > 0
);

redisClient.set(
graphqlQuery.query,
JSON.stringify(borrowers),
'EX',
intervalsInSeconds.twoMins
);

return borrowers;
};
Loading

0 comments on commit 7659c6f

Please sign in to comment.