Skip to content
Draft
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
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"node": "18.x"
},
"scripts": {
"dev": "nodemon --config nodemon.json src/app.ts",
"dev": "NODE_OPTIONS=--max_old_space_size=4096 nodemon --config nodemon.json src/app.ts",
"start": "node --require './appsignal.cjs' dist/app.js",
"start:local": "RUN_LOCAL_BUILD=true CLIMEM=8999 node -r climem dist/app.js",
"test": "NODE_ENV=test mocha",
Expand Down
18 changes: 18 additions & 0 deletions packages/api/src/controllers/v2/cico/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,21 @@ export const getCICO = (req: Request & ValidatedRequest<ListCICOProviderRequestS
.then(r => standardResponse(res, 200, true, r, {}))
.catch(e => standardResponse(res, 400, false, '', { error: e.message }));
};

export const getNewCICO = (req: Request & ValidatedRequest<ListCICOProviderRequestSchema>, res: Response) => {
const cicoProviderService = new CICOProviderService();

cicoProviderService
.getNew(req.query)
.then(r => standardResponse(res, 200, true, r, {}))
.catch(e => standardResponse(res, 400, false, '', { error: e.message }));
};

export const countCICO = (req: Request & ValidatedRequest<ListCICOProviderRequestSchema>, res: Response) => {
const cicoProviderService = new CICOProviderService();

cicoProviderService
.has(req.query)
.then(r => standardResponse(res, 200, true, r, {}))
.catch(e => standardResponse(res, 400, false, '', { error: e.message }));
};
65 changes: 61 additions & 4 deletions packages/api/src/routes/v2/cico/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Router } from 'express';

import { getCICO } from '~controllers/v2/cico';
import { countCICO, getNewCICO } from '~controllers/v2/cico';
import { listCICOProviderValidator } from '~validators/cico';

export default (app: Router): void => {
const route = Router();
app.use('/cico', route);
app.use('/new-cico', route);

/**
* @swagger
*
* /cico:
* /new-cico/count:
* get:
* tags:
* - "cico"
Expand Down Expand Up @@ -44,5 +44,62 @@ export default (app: Router): void => {
* "200":
* description: OK
*/
route.get('/:query?', listCICOProviderValidator, getCICO);
route.get('/count/:query?', listCICOProviderValidator, countCICO);

/**
* @swagger
*
* /new-cico:
* get:
* tags:
* - "cico"
* summary: Get cash-in/cash-out providers
* parameters:
* - in: query
* name: country
* schema:
* type: string
* required: false
* description: filter by country
* - in: query
* name: lat
* schema:
* type: number
* required: false
* description: latitude used for nearest location
* - in: query
* name: lng
* schema:
* type: number
* required: false
* description: longitude used for nearest location
* - in: query
* name: distance
* schema:
* type: number
* required: false
* description: distance in kilometers between the user location and providers
* - in: query
* name: type
* schema:
* type: number
* required: true
* description: type of cico to fetch
* - in: query
* name: offset
* schema:
* type: integer
* required: false
* description: offset used for pagination
* - in: query
* name: limit
* schema:
* type: integer
* required: false
* description: limit used for pagination
* responses:
* "200":
* description: OK
*/
route.get('/:query?', listCICOProviderValidator, getNewCICO);
};
48 changes: 48 additions & 0 deletions packages/api/src/routes/v2/cico/old.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Router } from 'express';

import { getCICO } from '~controllers/v2/cico';
import { listCICOProviderValidator } from '~validators/cico';

export default (app: Router): void => {
const route = Router();
app.use('/cico', route);

/**
* @swagger
*
* /cico:
* get:
* tags:
* - "cico"
* summary: Get cash-in/cash-out providers
* parameters:
* - in: query
* name: country
* schema:
* type: string
* required: false
* description: filter by country
* - in: query
* name: lat
* schema:
* type: number
* required: false
* description: latitude used for nearest location
* - in: query
* name: lng
* schema:
* type: number
* required: false
* description: longitude used for nearest location
* - in: query
* name: distance
* schema:
* type: number
* required: false
* description: distance in kilometers between the user location and providers
* responses:
* "200":
* description: OK
*/
route.get('/:query?', listCICOProviderValidator, getCICO);
};
2 changes: 2 additions & 0 deletions packages/api/src/routes/v2/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import global from './global';
import lazyAgenda from './lazyAgenda';
import learnAndEarn from './learnAndEarn';
import microcredit from './microcredit';
import oldCico from './cico/old';
import protocol from './protocol';
import referrals from './referrals';
import story from './story';
Expand All @@ -28,6 +29,7 @@ export default (): Router => {
protocol(app);
referrals(app);
cico(app);
oldCico(app);
lazyAgenda(app);

return app;
Expand Down
9 changes: 8 additions & 1 deletion packages/api/src/validators/cico.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Joi } from 'celebrate';

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

const validator = createValidator();

Expand All @@ -10,13 +11,19 @@ type ListCICOProviderType = {
lat?: number;
lng?: number;
distance?: number;
type?: number;
limit: number;
offset: number;
};

const queryListBorrowersSchema = defaultSchema.object<ListCICOProviderType>({
country: Joi.string().optional(),
lat: Joi.number().optional(),
lng: Joi.number().optional(),
distance: Joi.number().optional()
distance: Joi.number().optional(),
type: Joi.number().optional(),
limit: Joi.number().optional().default(config.defaultLimit),
offset: Joi.number().optional().default(config.defaultOffset)
});

interface ListCICOProviderRequestSchema extends ValidatedRequestSchema {
Expand Down
4 changes: 3 additions & 1 deletion packages/core/src/database/models/cico/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type IndividualDetails = {
phone: string;
};

type FiatConnectDetails = {};

export interface CICOProviderRegistry {
id: number;
name: string;
Expand All @@ -41,7 +43,7 @@ export interface CICOProviderRegistry {
type: number;
isCashin: boolean;
isCashout: boolean;
details: ExchangeDetails | MerchantDetails | IndividualDetails;
details: ExchangeDetails | MerchantDetails | IndividualDetails | FiatConnectDetails;

updatedAt: Date;
}
Expand Down
89 changes: 87 additions & 2 deletions packages/core/src/services/app/cicoProvider.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,100 @@
import { Op, QueryTypes, WhereOptions } from 'sequelize';
import { Op, QueryTypes, WhereOptions, fn, literal } from 'sequelize';

import { AppCICOProviderModel, CICOProviderRegistry } from '../../database/models/cico/providers';
import { models, sequelize } from '../../database';

enum CICOProviderType {
EXCHANGE = 0,
MERCHANT = 1,
INDIVIDUAL = 2
INDIVIDUAL = 2,
FIATCONNECT_PIXACCOUNT = 3
}

export default class CICOProviderService {
/**
* returns an array the types that are available under specific query
* @param query query params
*/
public async has(query: { country?: string; lat?: number; lng?: number; distance?: number }): Promise<number[]> {
const { country, lat, lng, distance } = query;
const conditions: any[] = [];

if (lat && lng && distance) {
conditions.push(
literal(
// distance is in meters, so we need to multiply by 1000
`(earth_box(ll_to_earth(${lat}, ${lng}), ${
distance * 1000
}) @> ll_to_earth(CAST(details->'gps'->>'latitude' AS FLOAT), CAST(details->'gps'->>'longitude' AS FLOAT)))`
)
);
}

if (country) {
conditions.push({
countries: {
[Op.or]: [{ [Op.contains]: [country] }, { [Op.contains]: ['11'] }]
}
});
} else {
conditions.push({ countries: { [Op.contains]: ['11'] } });
}

const resultCounts = await models.appCICOProvider.findAll({
attributes: ['type', [fn('COUNT', literal('*')), 'count']],
where: {
[Op.or]: conditions
},
group: ['type']
});

return resultCounts.map(r => r.dataValues.type);
}

public async getNew(query: {
country?: string;
lat?: number;
lng?: number;
distance?: number;
type?: number;
limit: number;
offset: number;
}) {
const { country, lat, lng, distance, type, limit, offset } = query;
const conditions: any[] = [];

if (lat && lng && distance) {
conditions.push(
literal(
`(earth_box(ll_to_earth(${lat}, ${lng}), ${
distance * 1000
}) @> ll_to_earth(CAST(details->'gps'->>'latitude' AS FLOAT), CAST(details->'gps'->>'longitude' AS FLOAT)))`
)
);
}

if (country) {
conditions.push({
countries: {
[Op.or]: [{ [Op.contains]: [country] }, { [Op.contains]: ['11'] }]
}
});
} else {
conditions.push({ countries: { [Op.contains]: ['11'] } });
}

const resultCounts = await models.appCICOProvider.findAll({
where: {
[Op.or]: conditions,
type: type ?? 0
},
offset,
limit
});

return resultCounts.map(r => r.toJSON());
}

public async get(query: { country?: string; lat?: number; lng?: number; distance?: number }) {
// when no query is provided
if (!query.country && !query.lat && !query.lng && !query.distance) {
Expand Down